Parent: Object
The Transaction class represents the result of an order (buy, sell, sell short, or cover short) that was placed either during a simulated backtest, or in the WealthLab Order Manager.
When developing a C# Coded Strategy, you obtain a Transaction instance as a result of the PlaceTrade method. You can modify certain properties of the Transaction, such as Quantity, to implement customized position sizing in your trading Strategy.
The commission that was applied to the order. In WealthLab, you establish commission in the Backtest Settings interface.
The signal entry date of the transaction. Corresponds to the price bar on which the trading signal was issued.
The date that the backtester filled the order during the simulation.
The price at which the backtester filled the order during the simulation.
Set this property to true to cause the backtester to fill the transaction even if there is not enough simulated capital.
This integer value is used internally by WealthLab to track which Building Block a particular Transaction belongs to. Avoid using this yourself. Instead, you can use the Tag property to store information in a Transaction.
Specifies a weight value to use for determining which transactions the backtester should fill in cases where there are more candidate transactions than available simulated capital. Transactions are sorted by this value during the backtest process, so higher values here will result in a higher priority.
using WealthLab.Backtest; using WealthLab.Core; using WealthLab.Indicators; namespace WealthScript2 { public class MyStrategy : UserStrategyBase { public override void Initialize(BarHistory bars) { _sma1 = SMA.Series(bars.Close, 8); _sma2 = SMA.Series(bars.Close, 20); _rsi = RSI.Series(bars.Close, 10); PlotIndicator(_sma1); PlotIndicator(_sma2); } public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { Transaction t = null; if (_sma1.CrossesOver(_sma2, idx)) t = PlaceTrade(bars, TransactionType.Buy, OrderType.Market); // Assign the highest priority to the lowest rsi (most oversold) by negation if (t != null) t.Weight = -_rsi[idx]; } else { if (_sma1.CrossesUnder(_sma2, idx)) PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } } //private variables SMA _sma1; SMA _sma2; RSI _rsi; } }
Assigning a value to this property allows WealthLab to immediately submit a "same-bar" limit order to exit a Position as soon as it is filled. This occurs both in backtesting and in live trading.
WealthLab's backtester will attempt to exit the Position at the limit price on the same bar it was entered. For real-world accuracy, use this option with market entry orders only. When applying same-bar exits to limit/stop entries, uncertainty may exist if the exit could have actually filled on the entry bar, which depends on the order of price movement following the entry. Therefore, the backtester will execute same-bar limit/stop orders for scenarios in which the exit can be determined to have occurred after the entry with certainty.
In live trading, WealthLab will issue the limit order as soon as it detects the Transaction is filled in the Order Manager.
Remarks
- In a future release, we will expose same-bar processing to our Granular Processing feature for more realistic results in backtesting.
- Contrast the example below that assigns the AutoProfitTargetPrice based on the last close (basis price) with that of AssignAutoStopTargetPrices(). The latter allows you to use the actual execution price as a basis for the same-bar exit orders.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript5 { public class SameBarAuto : UserStrategyBase { TimeSeries _band; public override void Initialize(BarHistory bars) { double pct = 0.95; _band = SMA.Series(bars.Close, 10) * pct; PlotTimeSeriesLine(_band, "band", "Price"); } public override void Execute(BarHistory bars, int idx) { if (HasOpenPosition(bars, PositionType.Long)) { //sell at open next bar PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } else { //buy at market if the low goes below the band, but closes above it if (bars.Low.CrossesUnder(_band[idx], idx) && bars.Close[idx] > _band[idx]) { Transaction t = PlaceTrade(bars, TransactionType.Buy, OrderType.Market); t.AutoProfitTargetPrice = bars.Close[idx] * 1.03; t.AutoStopLossPrice = bars.Close[idx] * 0.97; } } } } }
Assigning a value to this property allows WealthLab to immediately submit a "same-bar" stop order to exit a Position as soon as it is filled. This occurs both in backtesting and in live trading.
WealthLab's backtester will attempt to exit the Position at the stop price on the same bar it was entered. For real-world accuracy, use this option with market entry orders only. When applying same-bar exits to limit/stop entries, uncertainty may exist if the exit could have actually filled on the entry bar, which depends on the order of price movement following the entry. Therefore, the backtester will execute same-bar limit/stop orders for scenarios in which the exit can be determined to have occurred after the entry with certainty.
In live trading, WealthLab will issue the stop order as soon as it detects the Transaction is filled in the Order Manager.
Remarks
- In a future release, we will expose same-bar processing to our Granular Processing feature for more realistic results in backtesting.
- Contrast the example below that assigns the AutoStopLossPrice based on the last close (basis price) with that of AssignAutoStopTargetPrices(). The latter allows you to use the actual execution price as a basis for the same-bar exit orders.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript5 { public class SameBarAuto : UserStrategyBase { TimeSeries _band; public override void Initialize(BarHistory bars) { double pct = 0.95; _band = SMA.Series(bars.Close, 10) * pct; PlotTimeSeriesLine(_band, "band", "Price"); } public override void Execute(BarHistory bars, int idx) { if (HasOpenPosition(bars, PositionType.Long)) { //sell at open next bar PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } else { //buy at market if the low goes below the band, but closes above it if (bars.Low.CrossesUnder(_band[idx], idx) && bars.Close[idx] > _band[idx]) { Transaction t = PlaceTrade(bars, TransactionType.Buy, OrderType.Market); t.AutoProfitTargetPrice = bars.Close[idx] * 1.03; t.AutoStopLossPrice = bars.Close[idx] * 0.97; } } } } }
Returns true if this is an entry (Buy or Short) transaction.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript7 { public class MyStrategy : UserStrategyBase { TimeSeries _sma; public override void Initialize(BarHistory bars) { _sma = SMA.Series(bars.Close, 10); } public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { if (bars.Close.CrossesOver(_sma, idx)) PlaceTrade(bars, TransactionType.Buy, OrderType.Market, 0, "Xover"); else PlaceTrade(bars, TransactionType.Buy, OrderType.Limit, bars.Low[idx] * 0.98, "Dip buy"); } else { Position p = LastPosition; if (p.EntrySignalName.ToUpper() == "XOVER") { //sell this one after 5 bars if (idx + 1 - p.EntryBar >= 5) PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } else { //sell the dip buy the next day PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } } } //write the Signals to the Debug Log - first the exits, then entries public override void BacktestComplete() { WriteToDebugLog("*** Exit Signals ***"); foreach (Transaction t in Backtester.Orders) if (t.IsExit) OrderFormat(t); WriteToDebugLog("\r\n*** Entry Signals ***"); foreach (Transaction t in Backtester.Orders) if (t.IsEntry) OrderFormat(t); } private void OrderFormat(Transaction t) { string s = String.Format("{0,-6}{1,6} {2,-6}{3,8}", t.TransactionType, t.Quantity, t.Symbol, t.OrderType); if (t.OrderType == OrderType.Limit || t.OrderType == OrderType.Stop) s += (" @ " + t.OrderPriceDisplay); WriteToDebugLog(s); } } }
Returns true if this is an exit (Sell or Cover) transaction.
** Example **
- See Is Entry
Returns true if the Transaction was flagged as Non-Sufficient Funds by the backtester. These Transactions are still processed an tracked on a per symbol basis, in order to preserve the integrity of the Strategy. You can disable this behavior by turning off the Retain NSF Positions in the Advanced Strategy Settings of the Strategy Window.
The order price of the transaction. Does not apply for Market orders.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript123 { public class MyStrategy : UserStrategyBase { TimeSeries _sma; public override void Initialize(BarHistory bars) { _sma = SMA.Series(bars.Close, 10); } public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { if (bars.Close.CrossesOver(_sma, idx)) PlaceTrade(bars, TransactionType.Buy, OrderType.Limit, bars.Close[idx]); } else { PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } } //write the Signals to the Debug Log public override void BacktestComplete() { foreach (Transaction t in Backtester.Orders) { string s = String.Format("{0,-6}{1,6} {2,-6}{3,8}", t.TransactionType, t.Quantity, t.Symbol, t.OrderType); if (t.OrderType == OrderType.Limit || t.OrderType == OrderType.Stop) s += (" @ " + t.OrderPriceDisplay); WriteToDebugLog(s); } } } }
Contains the order type of the transaction. Possible values are OrderType.Market, OrderType.Limit, OrderType.Stop, OrderType.StopLimit, OrderType.FixedPrice, OrderType.LimitMove, OrderType.MarketClose, and OrderType.LimitClose. Market, Limit, Stop, StopLimit, MarketClose, and LimitClose simulate their corresponding broker order types, while FixedPrice is a theoretical order type intended to support situations where you want to establish a fixed price of an order.
See Enums > OrderType for more information
Returns the position type of the transaction, PositionType.Long for Buy and Sell orders, and PositionType.Short for Short and Cover orders.
Contains and assigns the number of shares or contracts in the transaction.
%Important!%
Assigning a Quantity to an Entry type Transaction (Buy/Short) in a Strategy always overrides the Position Sizer. When Staging/Placing an Exit type order (Sell/Cover), WealthLab will automatically change the signal quantity according to your selections in Preferences > Trading > Porfolio Sync.
The example demostrates how to use Transaction.Quantity for three purposes:
- Assign number of shares/contracts - Normally the Position Sizer in the Strategy Settings assign shares for Transactions. However, you can override the Sizer by accessing the Transaction object result of PlaceTrade() and assigning a Quantity in the Strategy.
- Split Positions - In a similar way, you can also assign Quantity for an exit signal. This effectively splits a position by assigning a number of shares less than the shares in the Position object.
- Access Signal Trade Quantity - The Backtester.Orders list holds the Transactions, if any, that were signaled on the final bar. The example shows how to generate a string of the Transaction signal details.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript1 { public class TransactionAndSplitDemo : UserStrategyBase { TimeSeries _sma; bool _positionSplit; public override void Initialize(BarHistory bars) { _sma = SMA.Series(bars.Close, 50); PlotStopsAndLimits(4); } public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { if (bars.Close.CrossesOver(_sma, idx)) { //Using the Transaction result from PlaceTrade, set the Quantity to 50 shares Transaction t = PlaceTrade(bars, TransactionType.Buy, OrderType.Limit, bars.Close[idx]); t.Quantity = 50; _positionSplit = false; } } else { Position p = LastOpenPosition; if (idx - p.EntryBar > 20) { ClosePosition(p, OrderType.Market, 0, "Time-based"); } else { // Sell 60% of the shares after a 8% closing profit if (!_positionSplit) { if (p.ProfitPctAsOf(idx) > 8) { Transaction t = PlaceTrade(bars, TransactionType.Sell, OrderType.Market, 0, "8% Profit"); t.Quantity = p.Quantity * 0.6; _positionSplit = true; } } // 20% profit target ClosePosition(p, OrderType.Limit, p.EntryPrice * 1.20, "20% Profit"); } } } //write the Signals to the Debug Log public override void BacktestComplete() { foreach (Transaction t in Backtester.Orders) { string s = String.Format("{0,-6}{1,6} {2,-6}{3,8}", t.TransactionType, t.Quantity, t.Symbol, t.OrderType); if (t.OrderType == OrderType.Limit || t.OrderType == OrderType.Stop) s += (" @ " + t.OrderPriceDisplay); WriteToDebugLog(s); } } } }
Sets a Position Metric value that will ultimately be passed to any Position object that is created as a result of this Transaction being filled by the Backtester. Position Metrics that your Strategy assigns here are available in certain Performance Visualizers, for example the Position Metrics Visualizer in the Power Pack Extension.
Allows you to save an optional signal name string along with the Transaction. This eventually gets passed to the EntrySignalName property of the resulting Position object.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript7 { public class MyStrategy : UserStrategyBase { TimeSeries _sma; public override void Initialize(BarHistory bars) { _sma = SMA.Series(bars.Close, 10); } public override void Execute(BarHistory bars, int idx) { if (OpenPositions.Count == 0) { if (bars.Close.CrossesOver(_sma, idx)) PlaceTrade(bars, TransactionType.Buy, OrderType.Market, 0, "Xover"); else PlaceTrade(bars, TransactionType.Short, OrderType.Limit, bars.High[idx] * 1.02, "Take a Shot"); } else { Position p = LastPosition; if (p.EntrySignalName.ToUpper() == "XOVER") { //sell this one after 5 bars if (idx + 1 - p.EntryBar >= 5) PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } else { //cover at the previous day close, or 3% stop loss PlaceTrade(bars, TransactionType.Cover, OrderType.Stop, p.EntryPrice * 1.03, "Stop Loss"); PlaceTrade(bars, TransactionType.Cover, OrderType.Limit, bars.Close[idx], "Get Out Quick"); } } } //write the Signals to the Debug Log - first the exits, then entries public override void BacktestComplete() { WriteToDebugLog("*** Exit Signals ***"); foreach (Transaction t in Backtester.Orders) if (t.IsExit) OrderFormat(t); WriteToDebugLog("\r\n*** Entry Signals ***"); foreach (Transaction t in Backtester.Orders) if (t.IsEntry) OrderFormat(t); } private void OrderFormat(Transaction t) { string s = String.Format("{0,-6}{1,6} {2,-6}{3,8}", t.TransactionType, t.Quantity, t.Symbol, t.OrderType); if (t.OrderType == OrderType.Limit || t.OrderType == OrderType.Stop) s += (" @ " + t.OrderPriceDisplay); else s += (new String(' ', 10)); s += ("\t" + t.SignalName); WriteToDebugLog(s); } } }
Works in conjunction with Transactions with an OrderType of StopLimit. You can assign a value to this property to establish the limit price of the StopLimit order. If unassigned, WL8 uses the OrderPrice (the stop price) of the order for its limit value.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Collections.Generic; namespace WealthScript1 { public class StopLimitDemo : UserStrategyBase { //create indicators and other objects here, this is executed prior to the main trading loop public override void Initialize(BarHistory bars) { StartIndex = 20; _h = Highest.Series(bars.High, 20); _l = Lowest.Series(bars.Low, 20); PlotIndicatorBands(_h, _l, WLColor.Fuchsia, 1, 20); } //execute the strategy rules here, this is executed once for each bar in the backtest history public override void Execute(BarHistory bars, int idx) { Position pos = LastPosition; if (!HasOpenPosition(bars, PositionType.Long)) { Transaction t = PlaceTrade(bars, TransactionType.Buy, OrderType.StopLimit, _h[idx], ""); t.StopLimitLimitPrice = _h[idx] + 2 * _ticks; } else { if (pos.PositionType == PositionType.Long) { Transaction t = PlaceTrade(bars, TransactionType.Sell, OrderType.StopLimit, _h[idx], ""); t.StopLimitLimitPrice = _l[idx] - 3 * _ticks; } } } Highest _h; Lowest _l; double _ticks = 0.05; } }
The symbol that the transaction was placed on.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript123 { public class MyStrategy : UserStrategyBase { TimeSeries _sma; public override void Initialize(BarHistory bars) { _sma = SMA.Series(bars.Close, 10); } public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { if (bars.Close.CrossesOver(_sma, idx)) PlaceTrade(bars, TransactionType.Buy, OrderType.Limit, bars.Close[idx]); } else { PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } } //write the Signals to the Debug Log public override void BacktestComplete() { foreach (Transaction t in Backtester.Orders) { string s = String.Format("{0,-6}{1,6} {2,-6}{3,8}", t.TransactionType, t.Quantity, t.Symbol, t.OrderType); if (t.OrderType == OrderType.Limit || t.OrderType == OrderType.Stop) s += (" @ " + t.OrderPriceDisplay); WriteToDebugLog(s); } } } }
If you assign a value to the Tag property of a Transaction instance that opened a new position (a Buy or Short TransactionTypes), that value will be assigned to the Tag property of the Position instance that is created.
Contains the type of transaction, possible values are TransactionType.Buy, TransactionType.Sell, TransactionType.Short or TransactionType.Cover.
using WealthLab.Backtest; using System; using WealthLab.Core; using WealthLab.Indicators; using System.Drawing; using System.Collections.Generic; namespace WealthScript123 { public class MyStrategy : UserStrategyBase { TimeSeries _sma; public override void Initialize(BarHistory bars) { _sma = SMA.Series(bars.Close, 10); } public override void Execute(BarHistory bars, int idx) { if (!HasOpenPosition(bars, PositionType.Long)) { if (bars.Close.CrossesOver(_sma, idx)) PlaceTrade(bars, TransactionType.Buy, OrderType.Limit, bars.Close[idx]); } else { PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } } //write the Signals to the Debug Log public override void BacktestComplete() { foreach (Transaction t in Backtester.Orders) { string s = String.Format("{0,-6}{1,6} {2,-6}{3,8}", t.TransactionType, t.Quantity, t.Symbol, t.OrderType); if (t.OrderType == OrderType.Limit || t.OrderType == OrderType.Stop) s += (" @ " + t.OrderPriceDisplay); WriteToDebugLog(s); } } } }