Is there a way to only enter into a position if at least 50% of the symbols in my Portfolio Backtest have a higher Close than they did in the previous bar?
Ideally I'd like to do this in a building block strategy rather than convert to a coded strategy. I've experimented with a Custom Indicator, but I'm having difficulty accessing other symbols' history in that context.
By sticking with building blocks, it would make it easier to optimize things like the required percentage, or the size of the bar lookback. But if the only way is a coded strategy, then I'll take that route.
Ideally I'd like to do this in a building block strategy rather than convert to a coded strategy. I've experimented with a Custom Indicator, but I'm having difficulty accessing other symbols' history in that context.
By sticking with building blocks, it would make it easier to optimize things like the required percentage, or the size of the bar lookback. But if the only way is a coded strategy, then I'll take that route.
Rename
QUOTE:
a way to only enter into a position if at least 50% of the symbols in my Portfolio Backtest have a higher Close than they did in the previous bar?
My initial thought would be to use the PreExecute{block} for this (Have you looked at a PreExecute example?), but that would definitely require some tricky C# coding.
But maybe there's an easier way if you can create a custom metric with IndexLab from your dataset portfolio. In the Help docs select
Extensions > Index Lab > Composite Indices
You're going to have to install the IndexLab extension before its help will appear in the Help docs. Carefully look through all the available "Composite Indices". If you can spot one that "approximates" your goal, then you're in business. That means you can build a custom index exclusively from your portfolio that approximately meets your requirements. And you can use your custom index to determine if it's the right time to Buy or not.
---
I would be interested in hearing other people's suggestions as well. Please post.
AI whipped-up the following solution, with a bit of guidance. It generated some wrong code in about 1 minute. After coaxing it, and telling the signature of PreExecute it belted-out the strategy in under 10 seconds :) Then, I told it to add comments with some important caveats about not using multi-threaded optimizers, and using proper strategy monitor settings. Please see the class comment header for details.
I only did some very basic testing.
I only did some very basic testing.
CODE:
using System; using System.Collections.Generic; using WealthLab.Backtest; using WealthLab.Core; namespace WealthLabStrategies.Analysis { /// <summary> /// PortfolioCondition is a Wealth-Lab 8 strategy that implements portfolio-wide market condition filtering /// for trade entry decisions. The strategy analyzes the percentage of symbols in the portfolio that have /// positive daily returns (close > previous close) and only enters positions when this percentage exceeds /// a configurable threshold. This approach helps identify favorable market conditions across the entire /// portfolio before committing capital to individual positions. /// Key Features: /// - Portfolio-wide condition analysis using all participating symbols /// - Configurable percentage threshold for market strength assessment /// - Shared static data to ensure consistent condition calculation across all symbols /// - Simple exit strategy based on holding period /// Use Cases: /// - Market regime filtering for systematic strategies /// - Risk management through portfolio-wide strength assessment /// - Timing entry signals based on broad market participation /// IMPORTANT: This strategy is NOT compatible with multithreaded optimizers in Wealth-Lab 8 /// due to its use of static shared data structures (_portfolioConditionByDate, _lastProcessedDate). /// Multithreaded execution would cause race conditions and data corruption. Use single-threaded /// optimization mode when running parameter optimization with this strategy. /// LIVE TRADING REQUIREMENTS: When running this strategy live (e.g., in Strategy Monitor), /// it is CRITICAL to check "Wait for all Updates before Processing" in the strategy monitor /// settings. This should ensure that data for ALL symbols in the portfolio is available at the /// current time before portfolio condition analysis begins. Without this setting, the strategy /// may make trading decisions based on incomplete portfolio data, leading to incorrect /// portfolio condition calculations and suboptimal trade entries. /// </summary> public class PortfolioCondition : UserStrategyBase { /// <summary> /// Static dictionary storing portfolio condition percentages by date. /// This data is shared across all symbol instances to ensure consistent /// portfolio-wide analysis. Key: DateTime, Value: Percentage of symbols with positive returns. /// </summary> private static Dictionary<DateTime, double> _portfolioConditionByDate; /// <summary> /// Tracks the last processed date to prevent duplicate calculations when /// multiple symbols are processed for the same datetime. This optimization /// ensures portfolio condition is calculated only once per bar across all symbols. /// </summary> private static DateTime _lastProcessedDate = DateTime.MinValue; /// <summary> /// Parameter controlling the minimum percentage of symbols that must have /// positive returns for the portfolio condition to be considered favorable. /// Range: 1-100%, Default: 50%, Step: 5% /// </summary> private readonly Parameter _requiredPercentageParam; /// <summary> /// Cached value of the required percentage parameter for performance optimization. /// Set during Initialize() to avoid repeated parameter lookups during execution. /// </summary> private double _requiredPercentage; /// <summary> /// Constructor that sets up the strategy parameters. /// Initializes the required percentage parameter with sensible defaults /// suitable for most market condition filtering applications. /// </summary> public PortfolioCondition() { // Configure the percentage threshold parameter // Default 50% means at least half the portfolio symbols must show positive returns _requiredPercentageParam = AddParameter("Required Percentage", ParameterType.Double, 50, 1, 100, 5); } /// <summary> /// Initialize method called once per symbol before backtesting begins. /// Caches parameter values for performance optimization during strategy execution. /// </summary> /// <param name="bars">Bar history for the current symbol being initialized</param> public override void Initialize(BarHistory bars) { base.Initialize(bars); // Cache the required percentage to avoid parameter lookups during execution _requiredPercentage = _requiredPercentageParam.AsDouble; } /// <summary> /// BacktestBegin is called once at the start of the entire backtest process, /// before any symbols are processed. This method initializes or resets the /// static data structures used for portfolio-wide condition tracking. /// </summary> public override void BacktestBegin() { base.BacktestBegin(); // Initialize fresh dictionary for storing portfolio conditions by date _portfolioConditionByDate = new Dictionary<DateTime, double>(); // Reset the last processed date tracker _lastProcessedDate = DateTime.MinValue; } /// <summary> /// PreExecute is called before Execute() for each bar and has access to all /// participating symbols' data for that datetime. This is where portfolio-wide /// analysis is performed to calculate the percentage of symbols with positive returns. /// The method implements a single-calculation-per-date optimization to avoid /// redundant processing when multiple symbols are evaluated for the same bar. /// </summary> /// <param name="dt">Current datetime being processed</param> /// <param name="participants">List of all BarHistory objects for symbols in the portfolio</param> public override void PreExecute(DateTime dt, List<BarHistory> participants) { base.PreExecute(dt, participants); // Optimization: Only process each datetime once across all symbols // This prevents redundant calculation when Execute() is called for each symbol if (_lastProcessedDate == dt) { return; } // Mark this date as processed _lastProcessedDate = dt; // Portfolio condition analysis: count symbols with positive daily returns var symbolsWithHigherClose = 0; // Count of symbols with close > previous close var validSymbolCount = 0; // Count of symbols with valid data for comparison // Iterate through all participating symbols to assess portfolio-wide condition foreach (var barHistory in participants) { // Get the index for the current datetime in this symbol's bar history var currentIdx = barHistory.IndexOf(dt); // Ensure we have both current and previous bar data for comparison if (currentIdx > 0 && currentIdx < barHistory.Count) { // Check if current close is higher than previous close (positive return) if (barHistory.Close[currentIdx] > barHistory.Close[currentIdx - 1]) { symbolsWithHigherClose++; } validSymbolCount++; } } // Calculate and store the portfolio condition percentage for this date if (validSymbolCount > 0) { // Calculate percentage of symbols with positive returns var percentage = (double) symbolsWithHigherClose / validSymbolCount * 100.0; // Store the result for use in individual symbol Execute() methods _portfolioConditionByDate[dt] = percentage; } } /// <summary> /// Execute method called for each symbol at each bar during backtesting. /// Implements the trading logic by checking portfolio conditions calculated /// in PreExecute() and making entry/exit decisions accordingly. /// Entry Logic: Enter long positions when portfolio condition percentage /// exceeds the required threshold, indicating broad market strength. /// Exit Logic: Simple time-based exit after holding for 5 bars. /// </summary> /// <param name="bars">Bar history for the current symbol being processed</param> /// <param name="idx">Current bar index in the symbol's bar history</param> public override void Execute(BarHistory bars, int idx) { // Skip the first bar since we need previous bar data for comparison if (idx == 0) { return; // Need at least one previous bar for portfolio condition analysis } // Get the current datetime for portfolio condition lookup var currentDate = bars.DateTimes[idx]; // Check if portfolio condition data is available for this date if (_portfolioConditionByDate.TryGetValue(currentDate, out var portfolioPercentage)) { // ENTRY LOGIC: Enter long position if portfolio condition is favorable // Only enter if the percentage of symbols with positive returns meets our threshold if (portfolioPercentage >= _requiredPercentage) { // Ensure we don't already have an open long position for this symbol if (!HasOpenPosition(bars, PositionType.Long)) { // Place market buy order to enter long position PlaceTrade(bars, TransactionType.Buy, OrderType.Market); } } } // EXIT LOGIC: Simple time-based exit strategy if (HasOpenPosition(bars, PositionType.Long)) { var position = LastPosition; // Exit after holding the position for 5 bars // This is a simple example - real strategies might use more sophisticated exit rules if (position != null && idx >= position.EntryBar + 5) { // Place market sell order to close the long position PlaceTrade(bars, TransactionType.Sell, OrderType.Market); } } } } }
If a symbol has a higher Close than the previous bar, then it has a ROC(Close, 1) of greater than zero. You can use the IndexLab CompInd% to compose this indicator easily, and use it in a Building Block just like any other indicator.
Thank you for the ideas, everyone! I had never tried IndexLab up until this point, it seems the time has now come.
QUOTE:
If a symbol has a higher Close than the previous bar, then it has a ROC(Close, 1) of greater than zero. You can use the IndexLab CompInd%
Thanks for that insight. I think there are multiple ways to use IndexLab here, and that might give the IndexLab approach an edge over the PreExecute approach, although both have their advantages and disadvantages.
If you are going to use IndexLab, then I would create a special "sentinel dataset" to build your index from that remains stable over time. That way one can change the makeup of the main trading dataset without changing the sentinel dataset so IndexLab won't need to recompute the index for each trading dataset change (which would be time consuming).
So which AI engine was used to write the PreExecute solution in Post #2?
Github Copilot Chat in Jetbrains Rider utilizing the Claude Sonnet 4 model.
Your Response
Post
Edit Post
Login is required