Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
How to calculate the cash available

I'm sure this has been answered somewhere before but I've have no luck in finding it. I would like to calculate the number of shares to place a buy order for based on how much cash is available. It seems to keep placing orders based on my starting amount + profits and not whats actually available when using context.portfolio.cash . I tried using context.portfolio.cash + context.portfolio.capital_used and still not the results I'm looking for (keep in mind capital_used is a negative number). Excuse me if this seems like a "stupid" question, I'm very new to python and Quantopian. Thanks!

16 responses

Can some one please respond?

Good question. How much cash do I have? One doesn't generally need the amount of cash if ordering is done with order_optimal_portfolio. That method takes care of the gritty details. However, if you are calculating the number of shares to order on your own, then the available cash is important to keep leverage under control.

If one doesn't have any short positions then context.portfolio.cash will be the actual cash in the account. If there is the potential for short positions then the following is a better approach

    actual_cash = context.portfolio.portfolio_value - context.portfolio.positions_value  
    available_cash = max(actual_cash, 0)

There is the possibility the actual cash could be negative (ie you've dipped into margin) so the cash available to open new positions would be only if it's positive. This of course assumes you are trying to maintain a margin of 0 or less. It get's a bit more involved if you want to consider margin in your 'buying power'.

The other thing this assumes is there are no open orders. If one places orders only once a day then this will be the case. However, if there is the chance of open orders then this available cash needs to be reduced by the anticipated cost of those orders. Again, this is a bit more involved because one needs to guess at what those orders will actually fill at along with calculating the commissions on those orders. If one is placing orders once a day then this isn't an issue.

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.

Thank you for your prompt response. I implanted the code you mentioned, yet the algo is still buying 5x the amount of starting capital that I gave to it. In other words, starting capital is 10,000 (through the input, not through my code); and on day one it is buying up $50k of stock.

I am not familiar with optimal function. I use the standard order because that is transferable to a platform that allows for live trading.

Let me know if you can diagnose with this or if you need more code:
(It does run - no errors) but doesn't spit out what I was expecting). Perhaps I should also mention that I want to use $500 per company - however many shares that may be ONLY if the portfolio has $500 available.

def order_picks(context,data):

context.total_cost_per_trade = 500  
actual_cash = (context.portfolio.portfolio_value - context.portfolio.positions_value)  
available_cash = max(actual_cash, 0)  

for stock in context.stocks.index:  
        if available_cash > 500:  
            order_value(stock, 500)  

for stock in context.portfolio.positions.keys():  
    if stock not in context.stocks:  
        if data.can_trade(stock):  
            order_target_percent(stock,0)  

Ok so I verified that it does buy $500 worth of each company, the problem is that it buys every stock in my pipeline. I am wondering if with the code you provided, because everything is being bought at one time it thinks the funds are still there because they haven't been technically purchased yet?

The backtesting framework is a discrete simulation. Events, such as filling orders, happen at discrete minute intervals. So, in the code above, an order is placed for each stock in 'context.stocks.index' all at one time. Those orders do not begin to be executed and filled until the next minute. Depending upon the slippage model and liquidity of a security, orders may not be completely filled for many minutes (or sometimes not at all). This is also very typical of how orders would be entered and filled in actual trading.

The code above does not take into account cash that has been 'committed' to orders. So, you are correct that the cash is still in the account.

You could maybe just open a fixed number of positions. Something like this

context.total_cost_per_trade = 500  
actual_cash = (context.portfolio.portfolio_value - context.portfolio.positions_value)  
available_cash = max(actual_cash, 0)  

qty_of_positions_to_open = available_cash // context.total_cost_per_trade  
positions_to_open = context.stocks.head(qty_of_positions_to_open).index

for stock in positions_to_open:  
    if data.can_trade(stock):  
        order_value(stock, context.total_cost_per_trade)  

for stock in context.portfolio.positions.keys():  
    if stock not in context.stocks.index:  
        if data.can_trade(stock):  
            order_target_percent(stock,0)  

You maybe want to be a bit more selective which positions to open. Above simply takes the first ones in the index which is by default sorted by SID. Maybe select the securities based upon some more relevant condition.

Typically algos do not order a fixed dollar amount. The issue is they don't scale as you backtest over a long period and different portfolio sizes. Ordering as a percent of the portfolio value mitigates some of these issues. One other comment about the code above is how existing holdings are dealt with. The algo will increase the position size of an existing holding. This may be intentional?

from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import RSI
from quantopian.pipeline.factors import SimpleMovingAverage
from quantopian.pipeline.factors import AverageDollarVolume
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
import talib
import pandas as pd
import numpy as np
from scipy import stats

def initialize(context):
pipe = Pipeline()
attach_pipeline(pipe, name='my_pipeline')
SMA_20 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=20)
price = USEquityPricing.close.latest
price_above_SMA20 = (price >= SMA_20)
volume = USEquityPricing.volume.latest
volume_1m = (volume > 1000000)
pipe.add(SMA_20, 'SMA_20')
pipe.set_screen(volume_1m & price_above_SMA20)

context.SL_Manager = StopLoss_Manager()

schedule_function(order_picks, date_rules.every_day(), time_rules.market_open())  
schedule_function(context.SL_Manager.manage_orders, date_rules.every_day(), time_rules.market_open())

def before_trading_start(context, data):
# Access results using the name passed to attach_pipeline.
results = pipeline_output('my_pipeline')
print(results.head(40))

# Store pipeline results for use by the rest of the algorithm.  
context.stocks = results  

def order_picks(context,data):

context.total_cost_per_trade = 500  
actual_cash = (context.portfolio.portfolio_value - context.portfolio.positions_value)  
available_cash = max(actual_cash, 0)  

qty_of_positions_to_open = available_cash // context.total_cost_per_trade  
positions_to_open = context.stocks.head(qty_of_positions_to_open).index

for stock in positions_to_open:  
    if data.can_trade(stock):  
        order_value(stock, context.total_cost_per_trade)  

for stock in context.portfolio.positions.keys():  
    if stock not in context.stocks.index:  
        if data.can_trade(stock):  
            order_target_percent(stock,0)  

class StopLoss_Manager:
"""
Class to manage to stop-orders for any open position or open (non-stop)-order. This will be done for long- and short-positions.

Parameters:  
    pct_init (optional),  
    pct_trail (optional),  
    (a detailed description can be found in the set_params function)  

Example Usage:  
    context.SL = StopLoss_Manager(pct_init=0.005, pct_trail=0.03)  
    context.SL.manage_orders(context, data)  
"""  

def set_params(self, **params):  
    """  
    Set values of parameters:  

    pct_init (optional float between 0 and 1):  
        - After opening a new position, this value  
          is the percentage above or below price,  
          where the first stop will be place.  
    pct_trail (optional float between 0 and 1):  
        - For any existing position the price of the stop  
          will be trailed by this percentage.  
    """  
    additionals = set(params.keys()).difference(set(self.params.keys()))  
    if len(additionals)>1:  
        log.warn('Got additional parameter, which will be ignored!')  
        del params[additionals]  
    self.params.update(params)  

def manage_orders(self, context, data):  
    """  
    This will:  
        - identify any open positions and orders with no stop (short and long)  
        - create new stop levels  
        - manage existing stop levels  
        - create StopOrders with appropriate price and amount  
    """  
    self._refresh_amounts(context)  

    for sec in self.stops.index:  
        cancel_order(self.stops['id'][sec])  
        if not data.can_trade(sec): continue  
        if self._np.isnan(self.stops['price'][sec]):  
            stop = (1-self.params['pct_init'])*data.current(sec, 'close')  
        else:  
            o = self._np.sign(self.stops['amount'][sec])  
            new_stop = (1-o*self.params['pct_trail'])*data.current(sec, 'close')  
            stop = o*max(o*self.stops['price'][sec], o*new_stop)  

        self.stops.loc[sec, 'price'] = stop  
        self.stops.loc[sec, 'id'] = order(sec, -self.stops['amount'][sec], style=StopOrder(stop))  

def __init__(self, **params):  
    """  
    Creatin new StopLoss-Manager object.  
    """  
    self._import()  
    self.params = {'pct_init': 0.07, 'pct_trail': 0.07}  
    self.stops = self._pd.DataFrame(columns=['amount', 'price', 'id'])  
    self.set_params(**params)  

def _refresh_amounts(self, context):  
    """  
    Identify open positions and orders.  
    """  

    # Reset position amounts  
    self.stops.loc[:, 'amount'] = 0.  

    # Get open orders and remember amounts for any order with no defined stop.  
    open_orders = get_open_orders()  
    new_amounts = []  
    for sec in open_orders:  
        for order in open_orders[sec]:  
            if order.stop is None:  
                new_amounts.append((sec, order.amount))  

    # Get amounts from portfolio positions.  
    for sec in context.portfolio.positions:  
        new_amounts.append((sec, context.portfolio.positions[sec].amount))  

    # Sum amounts up.  
    for (sec, amount) in new_amounts:  
        if not sec in self.stops.index:  
            self.stops.loc[sec, 'amount'] = amount  
        else:  
            self.stops.loc[sec, 'amount'] = +amount  

    # Drop securities, with no position/order any more.  
    drop = self.stops['amount'] == 0.  
    self.stops.drop(self.stops.index[drop], inplace=True)  

def _import(self):  
    """  
    Import of needed packages.  
    """  
    import numpy  
    self._np = numpy  

    import pandas  
    self._pd = pandas  

TypeError: cannot do slice indexing on with these indexers [200.0] of
There was a runtime error on line 47.

bump

The error stems from the following lines of code

qty_of_positions_to_open = available_cash // context.total_cost_per_trade  
positions_to_open = context.stocks.head(qty_of_positions_to_open).index

The variable 'qty_of_positions_to_open' can be a real number (eg the value 200.0 which was alluded to in the error message). The pandas method head doesn't like getting a real number as a parameter, it expects an integer. So, the fix is to coerce 'qty_of_positions_to_open' to an integer. Maybe like this

qty_of_positions_to_open = int(available_cash // context.total_cost_per_trade )  
positions_to_open = context.stocks.head(qty_of_positions_to_open).index

Try that.

Also, it's helpful if a backtest is attached to a post (rather than pasting the code). It makes it more convenient for the community to respond.

Thank you Dan! As always, you are very helpful and knowledgeable.

How do I post a backtest if the code did not run because it had the error?

One can generally attach a backtest whether or not it caused an error. Even if the backtest has a status of 'exception' you can still attach it. However, if the algo doesn't even get far enough to create an exception and there is a message like 'Something went wrong starting a full backtest.' Then a backtest won't even be created and therefore cannot be attached. If that's the case, one can comment out all the code by using triple quotation marks or something similar. Then add the following at the top

def initialize(context):  
    pass  

All an algo needs to be considered valid is an initialize function. It doesn't even need any code in it. This way at least the code can be seen and perhaps run simply by removing the comments.

There may be other 'tricks' folks may have but that's what I do.

Good question.

That makes sense. Thank you for that clarification.

I uploaded the back test. Seems like the algo is still buying way more than it should. Did you get that issue as well?

Is there something in particular you are seeing which makes you feel the algo is 'buying way more than it should'? I added some record statements to show the available cash and quantity of positions to the algo above. It shows the available cash at the end of each day is positive. The algo doesn't seem to be ordering more than the available cash.

Typically one ensures an algo has ordering under control by looking at leverage. The daily leverage can be seen graphically on the backtest page by clicking on the 'Risk' tab and then the 'Leverage' tab. Leverage is actually a bit low in this algo (under .6 at times) which would imply 'buying less than it should'. Part of the reason for this is the unfilled orders. Looking at the logs, there are quite a few orders which are unfilled at the end of the day. This is often due to trading low liquidity stocks. The other reason for the low leverage is rounding down when placing orders. The logic tries to keep a $500 position in each stock. However, the order_target_value method can only order integer quantities of shares and will not order more than $500. The effect of this is to leave a little bit of available cash in one's account for each position.

Do you know if I am able to export the transactions (buy/sell) to excel?

Like most data on Quantopian you can manipulate and view the transactions in a notebook but one cannot export them. If you have the backtest ID then something like this in a notebook will fetch a dataframe with all the transactions. You could also get the orders and/or the end of day holdings in a similar fashion.

# Put your own backtest ID below  
my_backtest_id = '5d8ba9ba268a4c29d22e265c'  
bt = get_backtest(my_backtest_id)  
my_transactions = bt. pyfolio_transactions

display(my_transactions)

You can then use python or pandas methods to sort and filter the list. There is a little more info on how to do this in this post https://www.quantopian.com/posts/python-help-1 . Additionally, look at the docs for other data which can be fetched from the backtest object https://www.quantopian.com/docs/api-reference/research-api-reference#quantopian.research.AlgorithmResult (it's a long list so scroll down to find info on transactions and orders.

Your a great asset to this community. Thank you Dan