Back to Community
Leverage Problems, when trying to go into TLT safe haven

I am trying to design an etf rotation strategy that will long 3 stocks based on a ranking, only if spy_price>spy_MA200. If the spy_price<spy_MA200 I go into the safety asset. For some reason it is doubling the leverage when I go into the safety asset. I am not sure why it is doing this. Any help would be great!

Thanks,

Eric

Clone Algorithm
6
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
# Backtest ID: 58d2e9b1a67dbb1c492c4ae9
There was a runtime error.
7 responses

Close positions before opening others. Everyone would do well to adopt that mindset.
This has no margin. Well, ok, thirteen dollars.

    # Close first  
    schedule_function(close, date_rules.month_start(), time_rules.market_open())  
    # Rebalance every day, 1 hour after market open.  
    schedule_function(rebalance, date_rules.month_start(), time_rules.market_open(hours=1))

def close(context,data):  
    for stock in context.portfolio.positions:  
        if not data.can_trade(stock): continue  
        order_target(stock, 0)  
        log.info("Sell" + str(stock))

def rebalance(context,data):  
    # weight = context.output.score/context.output.score.sum()  
    # print "score: {}    Weight: {}".format(context.output.score, weight)  
    # leverage=1.0  
    spy_200=data.history(context.spy, "price", 200, "1d")  
    spy_mavg=spy_200.mean()  
    spy_price=data.current(context.spy, "price")  
    if data.can_trade(context.shy) and spy_price<spy_mavg:  
        # Do a check to make sure no positions before this 1.0 all-in  
        order_target_percent(context.shy, 1.0)  
        log.info("Safety Month")  
        return

    for stock in context.longs:  
        if not data.can_trade(stock): continue  
        if get_open_orders(stock):    continue  
        order_target_percent(stock, 1.0 / len(context.longs))  
        #order_target_value(stock, context.portfolio.cash / len(context.longs))  
        log.info("Buy" + str(stock))  

Hi Blue,

I have a couple of questions about the code.

1) What are value are you returning after the context.shy logic?
2) What does the if not data.can_trade(stock)?

It' s good code, I am just a little lost on how to replicate it.

Here is my backtest. Would you recommend anything I could do to improve the strategy? This is the 2 year backtest.

Thanks,

Eric

Clone Algorithm
6
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
# Backtest ID: 58d472a8bbf3d34bedecc3de
There was a runtime error.

Hi Eric,

This is a good example of closing before buying. something we could all benefit from, closing any positions we would be closing ahead of any buying rather than in the same minute.

Among the advantages, we have more control over what's happening, cope better with partial fills, reduce margin and see a more true picture of what our strategies can do, providing for a more solid footing on which to make further improvements to them.

To answer: The buy of context.shy is set to 100% so I added a return to prevent more ordering after that, which would account for some of the 48k margin in the original. Some might also be due to partial fills, very common out there and invisible unless we apply some code to see them. I've added that here.
I think of data.can_trade() as avoiding stocks with no price in a given minute and delistings, you can read more about that for definitive answers.

At the risk of overwhelming readers, I'll share with you what I'm seeing. Here's the gist of it:
The original code appears to have higher returns and doesn't really. It used 58k to make 40k, a profit/risk of about 70%.
Mine uses basically just the starting 10k to make 34k for profit/risk of 343%.
Original leverage hit 2.37. Mine 1.00.
With these changes, if it can handle starting capital of 50k (close to what yours risked), then PnL can be ~170k.
Again, the original risked ~50k, made 40k, could have risked 50k and made 170k.
Clone and try it, I'm curious how far off the prediction would be.

Original  
2017-03-21 13:00 _pvr:198 INFO PvR 0.0194 %/day   cagr 0.1   Portfolio value 50269   PnL 40269  
2017-03-21 13:00 _pvr:199 INFO   Profited 40269 on 58022 activated/transacted for PvR of 69.4%  
2017-03-21 13:00 _pvr:200 INFO   QRet 402.69 PvR 69.40 CshLw -48022 MxLv 2.37 RskHi 58022 MxShrt -3228  
2017-03-21 13:00 pvr:286 INFO 2003-01-15 to 2017-03-21  $10000  2017-03-24 21:02 US/Eastern  
Runtime 0 hr 40.4 min

With close() before buying  
2017-03-21 13:00 _pvr:210 INFO PvR 0.0962 %/day   cagr 0.1   Portfolio value 44398   PnL 34398  
2017-03-21 13:00 _pvr:211 INFO   Profited 34398 on 10013 activated/transacted for PvR of 343.5%  
2017-03-21 13:00 _pvr:212 INFO   QRet 343.98 PvR 343.53 CshLw -13 MxLv 1.00 RskHi 10013 MxShrt 0  
2017-03-21 13:00 pvr:298 INFO 2003-01-15 to 2017-03-21  $10000  2017-03-24 19:24 US/Eastern  
Runtime 0 hr 33.4 min  

Run my version and analyze the orders in the logging window to help spot ways to improve the strategy. I left out:
if context.track: track_orders(context, data)
... at the end of close(), you'll want to add that so that track_orders will let you know when those sells are entered. Right now it only shows when they go thru. And delete the pf_list stuff, didn't work, I deleted most of it and missed those two lines.

Clone Algorithm
4
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
# Backtest ID: 58d5aabf58688d179ab8e0c7
There was a runtime error.

Thanks Blue,

The return to the close function makes sense to me. I hate leverage errors because it always makes you think you have good returns. What do you think of this backtest? I think it was good in 2003, but now the returns are kinda flat, especially over the two year backtest. Thanks for giving me the code for the profit and loss, I appreciate it.

Why is there no buy_when_no_opens method layer on top of the existing logic?

@EB

leverage errors because it always makes you think you have good returns

Exactly. Margin passing itself off as profit.

My eyes cross in awe over those who understand the specifics of various strategies, I'm an engine-room guy so I can't say much about this backtest in relation to the market. One thing I noticed with track_orders turned on was the unfilled TLT orders, volume issues. Maybe fill in some of the TLT role with a similar stock? Another couple of tools here, be sure to run all the way to the end and scroll all the way down in the log for the summary. I think you'll find that TLT ends in a loss. There were times when it was way up, so consider taking profit. After a first run summmary, find 'track_orders': 0 and turn that on in another run for more detail. And hover over the custom chart here for PnLs. Click legend items to toggle each off/on.

@AK One might run rebalance as soon as the close of positions at market open are all filled , that would be great. It would just take some doing. A lot of processor time to be checking for open orders every minute for as long as necessary or until pulling the plug and canceling the remainder of unfilleds. The hammer in place of a screwdriver route is to just cancel before opening.

def cancel_oos(context, data):  
    oo = get_open_orders()  
    for sec in oo:  
        for order in oo[sec]: cancel_order(order.id)

def rebalance(context,data):  
    cancel_oos(context, data)  
    ...  
Clone Algorithm
4
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
# Backtest ID: 58d9aa81924fc517a0da9338
There was a runtime error.

Yes. It would need to be an async callback after an order "filled"