Cone8
 ( 4.98% )
- ago
Could be implemented as a special type of strategy that has a file format definition U.I. - much like the ASCII provider.

- Include broker export file formats for easy selection.
6
1,410
Solved
14 Replies

Reply

Bookmark

Sort
- ago
#1
It would be great if we could see all of the Backtest tabs for imported trades - this would be useful to assess the real performance of strategy trading.
0
- ago
#2
Any traction on this? I have been waiting for this feature :-)
Will like to do some analysis on my previous real trades.
0
- ago
#3
If it raises to the top of the wishlist it will be considered. Items in the works are highlighted.
0
Cone8
 ( 4.98% )
- ago
#4
Here's a stop gap measure that I whipped up last weekend -

Description:
Reads the historical trade file specified in line 17 of the code - you need to specify the location for your file there.

Historical Trade File Spec:
1. The first line in the file should specify the date format, e.g, MM/dd/yyyy
2. Use a tab or semicolon to separate the values: Date;TradeType;Symbol;Price;Shares (optional)
3. Shares are optional. If you don't specify shares, it will use the Position Sizing settings for trades
4. You should adjust your trades for splits that occurred after the trade date.

Sample file -
MM/dd/yyyy
12/09/2021;Buy;ZM;189.90;15
12/07/2021;Buy;AAPL;171.25;10
12/10/2021;Sell;ZM;188.10

Instructions:
1. Create a DataSet with the symbols to be traded and run a backtest on the DataSet.
2. Make sure the Data Range covers the trading dates!
3. Starting Equity / Margin needs to specified for the buying power required.
4. Position Size doesn't matter if you specify the number of shares.

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; using System.IO; using System.Linq; using System.Globalization; namespace WealthScript3 {    public class BacktestTradeFile : UserStrategyBase    {       static List<string> _lines = new List<string>();       static List<FileTrade> _trades = new List<FileTrade>();       const string _path = @"C:\Data\Trades\TradeFile.txt";       string _dtefmt = "yyyy-MM-dd";       CultureInfo _cultureInfo = new CultureInfo("");              public Transaction ExecuteAtPrice(BarHistory bars, DateTime dte, string tradeType, double price, double shares = 0 )       {          Transaction result = null;          TransactionType transactionType = TransactionType.Buy;          int idx = bars.IndexOf(dte, true);          if (idx < 0)             return null;          switch (tradeType.ToUpper())          {             case "BUY":                transactionType = TransactionType.Buy;                               break;             case "SELL":                transactionType = TransactionType.Sell;                break;             case "SHORT":                transactionType = TransactionType.Short;                break;             default:                transactionType = TransactionType.Cover;                break;          }                             result = PlaceTrade(bars, transactionType, OrderType.FixedPrice, price);                    if (result != null && price > 0 && shares > 0)             result.Quantity = shares;                    return result;       } public override void BacktestBegin() {          List <string> _lines = File.ReadLines(_path).ToList();          // The date format must be the first line in the file, e.g. MM/dd/yyyy          _dtefmt = _lines[0];          WriteToDebugLog(_lines.Count + ", " + _dtefmt);          bool skipFirst = true;          foreach (string line in _lines)          {             if (skipFirst)             {                skipFirst = false;                continue;             }                          string[] token = line.Split(new string[] { "\t", ";" }, StringSplitOptions.None);             double shares = 0;             if (token.Length > 4)                shares = Double.Parse(token[4]);                          FileTrade trade = new FileTrade             {                Dte = DateTime.ParseExact(token[0], _dtefmt, _cultureInfo),                TType = token[1],                Symbol = token[2],                Price = Double.Parse(token[3]),                Shares = shares             };                         _trades.Add(trade);          }       } public override void Initialize(BarHistory Bars)       {          StartIndex = 1;       }       public override void PreExecute(DateTime dt, List<BarHistory> participants)       {          // Must place the trade on the previous idx, so match the next trade date                   foreach (BarHistory bh in participants)          {             for (int n = _trades.Count - 1; n >= 0; n--)             {                FileTrade ft = _trades[n];                if (ft.Symbol != bh.Symbol)                   continue;                DateTime nextTradeDate = dt.GetNextTradingDate(bh);                if (nextTradeDate == ft.Dte)                {                   Transaction t = ExecuteAtPrice(bh, dt, ft.TType, ft.Price, ft.Shares);                   if (t != null)                      _trades.RemoveAt(n);                }             }          }       }       public override void BacktestComplete()       {          if (_trades.Count > 0)                      {             WriteToDebugLog("Trades not successful: ");             foreach (FileTrade ft in _trades)             {                WriteToDebugLog(String.Format("{0:d}\t{1}\t{2:N0}\t{3}\t{4:N4}", ft.Dte, ft.TType, ft.Shares, ft.Symbol, ft.Price));             }          }       }       public override void Execute(BarHistory Bars, int Idx)       {             }    }    public class FileTrade    {       public DateTime Dte;       public string TType;       public string Symbol;       public double Price;       public double Shares = 0;    } }


1
Glitch8
 ( 10.62% )
- ago
#5
Could we potentially use the SplitReverse Indicator to automate the adjustment of shares here? This is going a long way to fulfilling this request, perhaps a whole new Strategy type dedicated to this is overkill.
0
Cone8
 ( 4.98% )
- ago
#6
QUOTE:
SplitReverse Indicator to automate the adjustment of shares here
Sure, I can work that in and repost.
0
Cone8
 ( 4.98% )
- ago
#7
Version 2 - no need to modify your positions/shares for splits, and, you'll get a possible reason if a trade fails to fill.

Description:
Reads the historical trade file specified in line 15 of the code - you need to specify the location for your file there.

Historical Trade File Spec:
1. The first line in the file should specify the date format, e.g, MM/dd/yyyy
2. Use a tab or semicolon to separate the values: Date;TradeType;Symbol;Price;Shares (optional)
3. Shares are optional. If you don't specify shares, it will use the Position Sizing settings for trades

Sample file -
MM/dd/yyyy
12/09/2021;Buy;ZM;189.90;15
12/07/2021;Buy;AAPL;171.25;10
12/10/2021;Sell;ZM;188.10

Instructions:
1. Create a DataSet with the symbols to be traded and run a backtest on the DataSet.
2. Make sure the Data Range covers the trading dates!
3. Starting Equity / Margin needs to specified for the buying power required.
4. Position Size doesn't matter if you specify the number of shares in the file.

CODE:
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; using System.IO; using System.Linq; using System.Globalization; namespace WealthScript123 {    public class BacktestTradeFile : UserStrategyBase    {       const string _path = @"C:\Data\Trades\TradeFile.txt";              static List<string> _lines = new List<string>();       static List<FileTrade> _trades;             string _dtefmt = "yyyy-MM-dd";       CultureInfo _cultureInfo = new CultureInfo("");       const double epsilon = 1.0e-10;              public Transaction ExecuteAtPrice(BarHistory bars, DateTime tradeDate, string tradeType, double price, double shares = 0 )       {          Transaction result = null;          TransactionType transactionType = TransactionType.Buy;          int idx = bars.IndexOf(tradeDate, true);                   if (idx < 0)          {             return null;          }                    // Build 41 - WL7 won't return out-of-range trade. This check required to return null Transaction object to detect trade failure          if (price < bars.Low[idx] - epsilon || price > bars.High[idx] + epsilon)             return null;                    switch (tradeType.ToUpper())          {             case "BUY":                transactionType = TransactionType.Buy;                               break;             case "SELL":                transactionType = TransactionType.Sell;                break;             case "SHORT":                transactionType = TransactionType.Short;                break;             default:                transactionType = TransactionType.Cover;                break;          }                             result = PlaceTrade(bars, transactionType, OrderType.FixedPrice, price);                    if (result != null && price > 0 && shares > 0)             result.Quantity = shares;                    return result;       } public override void BacktestBegin() {          _trades = new List<FileTrade>();          List <string> _lines = File.ReadLines(_path).ToList();          // The date format must be the first line in the file, e.g. MM/dd/yyyy          _dtefmt = _lines[0];          //WriteToDebugLog(_lines.Count + ", " + _dtefmt, false);          bool skipFirst = true;          foreach (string line in _lines)          {             if (skipFirst)             {                skipFirst = false;                continue;             }                          string[] token = line.Split(new string[] { "\t", ";" }, StringSplitOptions.None);             double shares = 0;             if (token.Length > 4)                shares = Double.Parse(token[4]);                          FileTrade trade = new FileTrade             {                Dte = DateTime.ParseExact(token[0], _dtefmt, _cultureInfo),                TType = token[1],                Symbol = token[2],                Price = Double.Parse(token[3]),                Shares = shares             };             _trades.Add(trade);          }       } public override void Initialize(BarHistory Bars)       {          StartIndex = 1;                    // adjustment for splits          Bars.UserData = new SplitReverse(Bars, PriceComponents.Close) / Bars.Close;       }       public override void PreExecute(DateTime dt, List<BarHistory> participants)       {          // Must place the trade on the previous idx, so match the next trade date                   foreach (BarHistory bh in participants)          {             for (int n = _trades.Count - 1; n >= 0; n--)             {                FileTrade ft = _trades[n];                if (ft.Symbol != bh.Symbol)                   continue;                                                DateTime nextTradeDate = dt.GetNextTradingDate(bh);                               //WriteToDebugLog(dt.ToString("yyyy-MM-dd HH:mm:ss.ttt") + " >> " + nextTradeDate.ToString("yyyy-MM-dd HH:mm:ss.ttt"));                                if (nextTradeDate == ft.Dte)                {                   int idx = GetCurrentIndex(bh) + 1;                                     double price = ft.Price;                   double shares = ft.Shares;                   //correct historical trade price and shares for splits                   TimeSeries adj = (TimeSeries)bh.UserData;                   price /= adj[idx];                   shares *= adj[idx];                                      Transaction t = ExecuteAtPrice(bh, ft.Dte, ft.TType, price, shares);                   if (t != null )                      _trades.RemoveAt(n);                }             }          }       }       public override void BacktestComplete()       {          List<string> symbols = Backtester.Symbols.Select(x => x.Symbol).ToList();          DataRange dr = Backtester.Strategy.DataRange;          if (_trades.Count > 0)                      {             WriteToDebugLog("Trades not successful: ");             foreach (FileTrade ft in _trades)             {                string reason = "Check date, price, and backtest Data Range";                if (!symbols.Contains(ft.Symbol))                {                   reason = String.Format("*** {0} is not in the DataSet ***", ft.Symbol);                }                else if (dr.DataRangeType == DataRangeTypes.DateRange)                {                   if (ft.Dte < dr.StartDate || ft.Dte > dr.EndDate)                      reason = "Trade date is outside backtest Data Range";                }                WriteToDebugLog(String.Format("{0:d}\t{1}\t{2:N0}\t{3}\t{4:N4}\t{5}", ft.Dte, ft.TType, ft.Shares, ft.Symbol, ft.Price, reason));             }                      }          foreach (Position p in GetPositionsAllSymbols(true))          {             if (p.NSF)                WriteToDebugLog(String.Format("{0:d}\t{1}\t{2:N0}\t{3}\t{4:N4}\t{5}", p.EntryDate, "Entry", p.Quantity, p.Symbol, p.EntryPrice, "Not Sufficient Funds"));             }                 }       public override void Execute(BarHistory Bars, int Idx)       {             }    }    public class FileTrade    {       public DateTime Dte;       public string TType;       public string Symbol;       public double Price;       public double Shares = 0;    } }
0
Best Answer
Glitch8
 ( 10.62% )
- ago
#8
Should we bundle this with the WL7 Sample Strategies and call this request complete?
0
Cone8
 ( 4.98% )
- ago
#9
If this is going to be the solution, let me look at the trade export formats for our supported brokers and adapt the script.
1
Cone8
 ( 4.98% )
- ago
#10
Someone with a TD account please go to My Account > History & Statements > Transactions and do a csv download that contains a Short and Cover trade. What term is used for Short and Cover?

e.g., Buy and Sell is "Bought" and "Sold".

What's Short and Cover?
0
Cone8
 ( 4.98% )
- ago
#11
Feature request solved in Build 42 with a new strategy type - TradeHistory Strategy!

TradeHistory Strategies make it easier than ever to import a trade history, with auto-adjustment for splits. Trades that are outside the bar range are still "filled", which can happen for a variety of reasons (option exercise, after-market trading, etc. ).

If you have a TD or IB account, follow the instructions for the built-in parser. Set the backtest date range, and run. That's all there is to it.

Let us know the trade file formats for other brokers and we'll include a parser for them!
0
- ago
#12
Very nice implementation!

This will be great to analyse the effect of slippage, for example.
2
Cone8
 ( 4.98% )
- ago
#13
I always like to remind everyone that if you're an EOD trader, you can trade without slippage using a MOO order, which is executed at the primary market's auction opening price - the price Wealth-Data uses for it's Open.

For any other EOD provider, the open is simply the first full-lot trade (100 shares) that can occur on any exchange - and could be "far" from the inside market. See examples every day at https://www.wealth-data.com

Enable the MOO option in the Trading Preferences. See the User Guide for more info.
0
- ago
#14
Thank you Cone!

In my case, I trade mostly index future contracts; and the open is not that liquid sometimes. Therefore, I don't use market orders on open to avoid influencing the market by being the one making the price: in which case I would not have any slippage; however the movement was caused by me...

I know it sounds a bit of a temporal causal loop paradox; but I prefer to enter 1-2m after and then calculate the slippage from it.
2

Reply

Bookmark

Sort