7
$\begingroup$

I've developed some software which generates sets of trades, and I'd like to backtest those trades. My software currently outputs a CSV file with details of each trade:

2011-03-31,MSFT,Buy,100
2011-04-02,AAPL,Buy,50
2011-05-10,MSFT,Sell,100

Is there any backtesting software out there that lets you bring in a set of your own trades, and see how it would have done? All the software I've found so far requires to you write your algos directly in the package, and doesn't simply let you say 'Buy X, Sell Y'.

Edit based on comments:

  • I don't include prices or commissions in my CSV because my play here is a long term play (timescale is months or even years). Having the backtesting software use the VWAP (or even just the day's close) is fine, and with most retail commissions fairly low I could either let the backtesting software add one in or just ignore it. I may not get perfect resolution but (I think) I'd be close enough.
  • I can't use any of the packages that I've found because my algo doesn't work on the traditional technicals. Instead I'm looking (mostly) at independent stuff, such as 13f data feeds.
  • I can definitely write something in R, or even in my own codebase, but I'm trying to save myself some work on my proof of concept.
$\endgroup$
6
  • 4
    $\begingroup$ Most likely there is not. But why would you want to do that? Your csv pattern above does not specify at all how you want the trades to be executed for performance attribution purposes. Do you want to get filled at the last traded price of the day, at the bid or offer or mid? You better write your own little code in R or even in Excel. Simply load the daily for the stocks in question, and iterate over your trades by pulling out the prices you want to simulate fills against. Done. Even with this you make tons of horrible assumptions that reflect anything but reality. $\endgroup$
    – Matt Wolf
    Apr 7, 2013 at 10:54
  • 1
    $\begingroup$ Hi dordal. I also think your trader orders are too much simplistic. That IS the problem. $\endgroup$
    – tagoma
    Apr 7, 2013 at 12:47
  • 2
    $\begingroup$ The first trading desk I worked for wrote a backtester in q/kdb+ that took this exact input. It assumed that it got filled on the day's VWAP and applied a transaction cost (slippage) to negatively impact the value. In retrospect, those were some generous assumptions, such as always getting the fill or always finding the borrow. And that trading desk is no longer around... $\endgroup$ Apr 7, 2013 at 13:01
  • $\begingroup$ Thanks guys. I just made some edits to my question to explain things further; @Freddy I'm curious if you still feel its too simplistic. $\endgroup$
    – dordal
    Apr 7, 2013 at 17:19
  • $\begingroup$ I think the complexity in your case is not the backtest itself (it's just a few cashflows in the range of months), the complexity is to describe to a (generic) backtesting software what you want out of it, and such a language is likely to be complex; possibly even a superset of FIX if you want to take care of pre-trade and post-trade stuff as well. Is it worth learning such a language? Yes, if it was standard. In your case, I think you're better off quickly writing the code yourself. $\endgroup$
    – hroptatyr
    Apr 7, 2013 at 23:19

5 Answers 5

16
$\begingroup$

You're not really asking how to backtest a strategy. You already have run a backtest to generate simulated trades. What you're asking for is a way to assess the performance of those simulated trades.

You can do this with the R package blotter. You'll need to setup your account and portfolio, then loop over each row in your CSV and call addTxn. For example:

trades <- read.csv("trades.csv")
symbols <- unique(trades$symbol)

# Set up a portfolio object and an account object in blotter
initPortf(name='default', symbols=symbols, initDate=initDate)
initAcct(name='default', portfolios='default', initDate=initDate, initEq=initEq)
verbose = TRUE

for(i in 1:NROW(trades)) {
  addTxn('default', Symbol=trades$symbol[i], TxnDate=trades$date[i],
    TxnPrice={"object-with-price"}, TxnQty=trades$quantity[i], TxnFees=0, verbose=verbose)
}

# Calculate P&L and resulting equity with blotter
updatePortf(Portfolio='default', Dates=CurrentDate)
updateAcct(name='default', Dates=CurrentDate)
updateEndEq(Account='default', Dates=CurrentDate)

# Look at performance
chart.Posn(Portfolio='defaut',Symbol='MSFT')
chart.Posn(Portfolio='defaut',Symbol='AAPL')
$\endgroup$
3
$\begingroup$

Answering my own question... based on the above comments and a lot of research, it looks like there aren't any packages out there that do this 'out of the box'. So coding your own is the best way to go.

$\endgroup$
1
$\begingroup$

With a backtesting library such as Backtesting.py and some Python, you could do something like:

import pandas as pd

trades = pd.read_csv('my_trades.csv',
                     index_col=0,
                     parse_dates=True,
                     infer_datetime_format=True)
buys = trades[trades.iloc[:,1] == 'Buy']
sells = trades[trades.iloc[:,1] == 'Sell']

from backtesting import Strategy

class MyTrades(Strategy):
    def next(self):
        if self.data.index in buys:
            self.buy()
        if self.data.index in sells:
            self.sell()
$\endgroup$
0
$\begingroup$

With Backtrader in Python should be easy.

For example:

from datetime import datetime
import backtrader as bt

class SmaCross(bt.SignalStrategy):
    def __init__(self):
        sma1, sma2 = bt.ind.SMA(period=10), bt.ind.SMA(period=30)
        crossover = bt.ind.CrossOver(sma1, sma2)
        self.signal_add(bt.SIGNAL_LONG, crossover)

cerebro = bt.Cerebro()
cerebro.addstrategy(SmaCross)

data0 = bt.feeds.YahooFinanceData(dataname='MSFT', fromdate=datetime(2011, 1, 1),
                                  todate=datetime(2012, 12, 31))
cerebro.adddata(data0)

cerebro.run()
cerebro.plot()

To use your own CSV data, you can define your own custom feed class, e.g.:

import itertools
...
import backtrader as bt

class MyCSVData(bt.CSVDataBase):

    def start(self):
        # Nothing to do for this data feed type
        pass

    def stop(self):
        # Nothing to do for this data feed type
        pass

    def _loadline(self, linetokens):
        i = itertools.count(0)

        dttxt = linetokens[next(i)]
        # Format is YYYY-MM-DD
        y = int(dttxt[0:4])
        m = int(dttxt[5:7])
        d = int(dttxt[8:10])

        dt = datetime.datetime(y, m, d)
        dtnum = date2num(dt)

        self.lines.datetime[0] = dtnum
        self.lines.open[0] = float(linetokens[next(i)])
        self.lines.high[0] = float(linetokens[next(i)])
        self.lines.low[0] = float(linetokens[next(i)])
        self.lines.close[0] = float(linetokens[next(i)])
        self.lines.volume[0] = float(linetokens[next(i)])
        self.lines.openinterest[0] = float(linetokens[next(i)])

        return True

Then load your data and add your strategy:

cerebro = bt.Cerebro()
data = MyCSVData(dataname='file.csv', fromdate=datetime(2019, 1, 1), todate=datetime(2019, 2, 28))
cerebro.adddata(data)
cerebro.addstrategy(MyStrategy)

Docs: https://www.backtrader.com/

$\endgroup$
0
$\begingroup$

TuringTrader (https://www.turingtrader.org/) might be worth a look. Or Zorro (https://www.zorro-trader.com/).

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.