Output N number of buy signals only to a text file

I've tried numerous ways and iterations to output only a set / specific number of signals to a text file, which would seem fairly straight forward, but can't seem to get it done. All of the different methods I've tried are too numerous to include so I've included my last attempt to simplify the process. I'm just trying to output the first 20 buy signals to the output file. Thank you.

 

Buy = Cross( MACD(), Signal()) ;

// Sell = Cross( Signal(), MACD() );


// IIf(Cum(Buy) == 0, counter = 0, null);
// IIf(Buy == true, counter = counter + 1, counter);


if( Nz( StaticVarGet( "InitializationDone" ) ) == 0 )
{
    // StaticVarRemove("InitializationDone*");

    StaticVarSet( "InitializationDone", 1 );
    // code for first execution
}

// IIf(StaticVarGet("InitializationDone") == 1, counter = 0, 0);
if( StaticVarGet( "InitializationDone" ) == 1 )
{
    counter = 0;
}


IIf( Buy == true, counter = counter + 1, counter = counter );



if( counter < 20 )
{

    if( LastValue( Buy ) )
    {
        // filepath = "C:\\ScanExport.sig";
        filepath = "C:\\Users\\Dave\\Alera\\TestAccount1\\1001\\ScanExportToday.sig";


        if( Status( "stocknum" ) == 0 )
        {
            // delete previous file before anything else
            fdelete( filepath );
        }

// open file in "share-aware" append mode
        fh = fopen( filepath, "a", True );

// proceed if file handle is correct
        if( fh )
        {
            lastbuyDT =  LastValue( ValueWhen( Buy, DateTime() ) ) ;

            // write to file
            // fputs( Name() + ", Last Buy: " + DateTimeToStr( lastBuyDT ) + "\n", fh );

            // write to sig file
            fputs( "BTO " + Name() + " " + "DAY" + " " + "LMT" + " " + Close + "\n", fh );

            // close file handle
            fclose( fh );
        }
        else
        {
            _TRACE( "Failed to open the file" );
        }
    }

    
}

/*
    AddColumn( BuyPrice, "Buy" );
    AddColumn( StaticVarGet( "InitializationDone" ), "Static Variable Value" );
    AddColumn( counter, "Counter Value" );
*/  



You have multiple errors/inefficiencies, but those probably aren't worth addressing until we understand your goal. Are you trying to capture the first 20 Buy signals generated for a single symbol, or the 20 highest-ranked signals for multiple symbols on a single bar?

@mradtke ideally the 20 highest-ranked signals for multiple symbols on a single bar. Thanks.

One way to accomplish that would be to add a mid-level CBT, which includes a bar-by-bar loop. For each bar, you can retrieve the top 20 entry signals and write them to a file before calling bo.ProcessTradeSignals().

Another approach would be use StaticVarGenerateRanks(). However, if you want the lines in your output file to be ordered by date, you'll have to jump through some hoops to have all the relevant information at the right time.

1 Like

@mradtke thank you for the suggestions, I'll do some research and see if I can implement. I was thinking that the Static Variable method may be best (easiest) for me at my current level of understanding.

@radtke I've made some progress and I'm able to generate the text file so that it outputs only signals and gives the overall rank for the signals. I've included the code below just using MACD crossover as the buy signal and ranking via ROC.

The ideal situation would be to output the text file in order of ranking. I believe the signals are being done in one pass so I currently don't have access down to the symbol, signal and rank level per symbol. I tried using the sort function on the rank1 array since it doesn't matter what the ranking number is it just matters that the lowest rank is ordered first, but I was getting weird and unexpected results.

I've used the high level functionality of the CBI / Custom Backtester Interface so I'll do a bit more homework on that and see if I can figure out how to better manipulate the rankings. Thanks for the suggestions it was very helpful.

Regards,

Dave

// global rank1;
// global values1;

// Lookback periods
MOMLookback = Param( "Trend Look back Days", 126, 1, 365, 1 );

// Buy Signal
Buy = Cross( MACD(), Signal() );


if( LastValue( Buy ) )
{

// filepath = "C:\\ScanExport.sig";
    filepath = "C:\\Users\\Dave\\Alera\\TestAccount1\\1001\\ScanExportToday - 3.sig";


    if( Status( "stocknum" ) == 0 )
    {
        // delete previous file before anything else
        fdelete( filepath );

        StaticVarRemove( "values1*" );
        StaticVarRemove( "rank1*" );
        StaticVarRemove( "rank1values1*" );

// Manage access to watchlist(s)
        if( GetOption( "ApplyTo" ) == 2 )
        {
            wlnum = GetOption( "FilterIncludeWatchlist" );
            List = CategoryGetSymbols( categoryWatchlist, wlnum ) ;
        }

        else
            if( GetOption( "ApplyTo" ) == 0 )
            {
                List = CategoryGetSymbols( categoryAll, 0 );
            }
            else
            {
                Error( "The formula works fine if your ApplyTo setting is 'Filter' or 'All' " );
            }


// Static Variables
// Rank1 - Momentum

        for( m = 0; ( Symbol = StrExtract( List, m ) )  != "";  m++ )
        {
            SetForeign( symbol );
									
	    values1 = ROC(Close, MOMLookback);
			
            RestorePriceArrays();
            StaticVarSet( "values1" + symbol, values1 );
            _TRACE( symbol );
        }

        StaticVarGenerateRanks( "rank1", "values1", 0, 1224 );
    }


    symbol = Name();

    values1 = StaticVarGet( "values1" + symbol );
    rank1 = StaticVarGet( "rank1values1" + symbol );

    // AddColumn( values1, "Momentum ROC" );
    // AddColumn( rank1, "Rank Momentun ROC" );


	Filter = 1;

    SetSortColumns( 4 );

    // AddColumn( Buy, "MACD Status", format = 1, colorDefault, IIf( Buy, bkcolor = coloryellow, bkcolor = colordefault ) ) ;


// open file in "share-aware" append mode
    fh = fopen( filepath, "a", True );

// proceed if file handle is correct
    if( fh )
    {

        lastbuyDT =  LastValue( ValueWhen( Buy, DateTime() ) ) ;
		
        // write to sig file
        fputs( "BTO " + symbol + " " + "DAY" + " " + "LMT" + " " + Close + " " + "Rank = " + rank1 + "\n", fh );

        // close file handle
        fclose( fh );
    }
    else
    {
        _TRACE( "Failed to open the file" );
    }
 

}

 
symbol = Name();

values1 = StaticVarGet( "values1" + symbol );
rank1 = StaticVarGet( "rank1values1" + symbol );

// Filter = 1; -- do not use Filter = 1 unless you want it to show ALL SYMBOLS.  without filter = 1 will only show buy signals in the exploration

AddColumn( values1, "Momentum ROC" );
AddColumn( rank1, "Rank Momentun ROC" );
AddColumn( Buy, "MACD Status", format = 1, colorDefault, IIf( Buy, bkcolor = coloryellow, bkcolor = colordefault ) ) ;

SetSortColumns(4);


Your rank1 array contains the ranking for each bar for a single symbol. Therefore, it wouldn't make much sense to sort that array. Glad that you got something working that you're happy with.

@mdradtke

I'm still trying to sort the ranking from lowest value to highest value (when output to text file) with no luck.

The other issue I am having is that the script was working perfectly (except for being able to sort the rank1 values from lowest to highest when output to text file), and now when I run it, the rank values are mostly empty. I'm hitting the wall here. I don't know why either a simple sort or positionscore command won't sort the rank values.

@mradtke @Tomasz @portfoliobuilder any suggestions for fixing this code to accomplish the above? Going a bit nutty here so any help much appreciated!

@DaveDM, I think you're getting confused between variables that are defined for a single symbol at a time (rank1, PositionScore, etc.) and AmiBroker's ability to creating rankings across multiple symbols using StaticVarGenerateRanks().

It appears that you're only trying to create the Signal file for the last day in your date range, and that you would like the entries in the Signal file to be ordered by their rank. Is that correct? If so, that would be quite easy to handle with a mid-level CBT.

Yes, that is essentially what I am trying to do, create a rank for the current (or specified) day.

Regards,

Dave

The easiest way to get the values you want would be to use an Exploration. I assume you don't want to do that because when you save the Exploration results you will have a CSV file rather than a space-delimited file like you are creating now. if you're feeding the signal file to your broker or something similar, then you would need to do some post-processing of the file to replace commas with spaces (or equivalent).

As stated earlier, the simplest way to create a text file in your own format and ordered by PositionScore would be to write a mid-level CBT.

thanks @mradtke
I was trying to avoid the Exploration and CSV route but if I hit the wall that will be the back up plan, export a CSV and process that file using python or similar. I would prefer to avoid the extra complexity and steps.

I've tried the mid level CBT with no luck (so far), I'll have to spend more time on it. I haven't really been able to find much in terms of usage examples, specifically for what I'm trying to do.

Do you have a working mid-level CBT that correctly runs your back test? If so, you should be able to insert some code just before the bo.ProcessTradeSignals() call. That inserted code should loop through all the signals for the current bar using bo.GetFirstSignal() / bo.GetNextSignal(). For each signal that is an Entry signal, write it out to the text file. If you're doing that and it's not working, post your code and describe the failure.

@mradtke thanks I'll have a look at doing that next.

I'm curious why I can't just sort the ranked array and output it to the text file in sorted order. A curious thing happens when I try to sort the array... the "Rank Momentum ROC" column is the ranked array after applying the sort function to it whereas the "SV Rank MOM ROC" is the actual array created using StaticVarGenerateRanks. For some reason when I sort the array created of the ranking using stat variables it actually changes the values. As you can see, the two columns "should" in theory be the same but the values are different. The "SV Rank MOM ROC" column contains the correct values. I have no idea what happened to the other column.

in other words, the values in StaticVarGet("rank1values1" + symbol) are correct but when I apply any kind of sort to that array the values change drastically.

The code used to create this was...

 if( Status( "stocknum" ) == 0 )
    {
        // delete previous file before anything else
        fdelete( filepath );

        StaticVarRemove( "values1*" );
        StaticVarRemove( "rank1*" );
        StaticVarRemove( "rank1values1*" );

// Manage access to watchlist(s)
        if( GetOption( "ApplyTo" ) == 2 )
        {
            wlnum = GetOption( "FilterIncludeWatchlist" );
            List = CategoryGetSymbols( categoryWatchlist, wlnum ) ;
        }

        else
            if( GetOption( "ApplyTo" ) == 0 )
            {
                List = CategoryGetSymbols( categoryAll, 0 );
            }
            else
            {
                Error( "The formula works fine if your ApplyTo setting is 'Filter' or 'All' " );
            }


// Static Variables
// Rank1 - Momentum

        for( m = 0; ( Symbol = StrExtract( List, m ) )  != "";  m++ )
        {
            SetForeign( symbol );
									
			values1 = ROC(Close, MOMLookback);
			
            RestorePriceArrays();
            StaticVarSet( "values1" + symbol, values1 );
            _TRACE( symbol );
        }

        StaticVarGenerateRanks( "rank1", "values1", 0, 1224 );
        // rank1 = Sort(StaticVarGet("rank1values1" + symbol), 0, -1, indexmode = false);
        rank = StaticVarGet("rank1values1" + symbol);
        rank1 = Sort(rank, 0, -1, indexmode = false);

image

@DaveDM, with all due respect, you're not listening. Your rank array is assigned like this:

rank = StaticVarGet("rank1values1" + symbol);

That means that you are getting the ranking for each bar for the symbol whose name is in the symbol variable. Since you're calling the code above after looping through all the symbols, the symbol variable contains whatever it was last assigned before exiting the loop.

What you are not getting in the rank array is a collection of all symbols for a single bar. To get the top-ranked symbols, I believe you would need to use the "top ranks" mode of StaticVarGenerateRanks() and also use 'StaticVarGetRankedSymbols()'. See https://www.amibroker.com/guide/afl/staticvargetrankedsymbols.html. I say "I believe..." because I have not used this functionality myself, as I prefer to accomplish this type of task via the CBT.

1 Like

thanks again @mradtke
Part of my issue is not understanding exactly how these functions are treating the underlying arrays, you are correct, I did not realize that I wasn't getting a per bar ranking of all the symbols in my list. I've been working on this for a while now and admittedly getting a bit frustrated :confused:

Can you share a usage example of ranking in a mid level CBT as you've suggested, I want to compare to the ugly mess I'm making -- admittedly I'm too embarrassed to post my current CBT code because, well, it's embarrassing lol

@mradtke I've tried a number of different ways to accomplish this. My attempt at using the mid-level CBT is below. It is not outputting the text file at all, however, the exploration is working reasonably well.

SetOption("usecustombacktestproc", True);

	buysignal = Cross( MACD(), Signal() );
	Buy = buysignal ;

SetCustomBacktestProc("");
// CBT mid-level 
if( Status( "action" ) == actionPortfolio )
{
    bo = GetBacktesterObject();
    bo.PreProcess();

	buysignal = Cross( MACD(), Signal() );
	PositionScore = StaticVarGet( "values" +  symbol );
	
    for( bar = 0; bar < BarCount; bar++ )
    {
		Cnt = 0;
        for( sig = bo.getfirstsignal( bar ); sig; sig = bo.GetNextSignal( bar ) )
        {
			
            filepath = "C:\\Users\\Dave\\Alera\\TestAccount1\\1001\\ScanExportToday.sig";
			
            if( Status( "stocknum" ) == 0 )
            {
                // delete previous file before anything else
                fdelete( filepath );
            }

// open file in "share-aware" append mode
            fh = fopen( filepath, "a", True );

// proceed if file handle is correct
            if( fh )
            {
                lastbuyDT =  LastValue( ValueWhen( Buy, DateTime() ) ) ;
			
			
			if ((Buy[sig] OR sig.isentry() OR buysignal) AND Cnt <= 10)
			{
                // write to file
                // IIf( buysignal, fputs( "Rank = " + StaticVarGet( "rankvalues" +  symbol ) + " " + symbol + "\n", fh ), fputs( "--" + "\n", fh ) );
				fputs( "Rank = " + StaticVarGet( "rankvalues" +  symbol ) + " " + symbol + "\n", fh );
				Cnt++;
                
                // close file handle
                fclose( fh );
             }   
            }
            else
            {
                _TRACE( "Failed to open the file" );
            }

            bo.ProcessTradeSignals( bar );
        }

        bo.PostProcess();
    }

}

// ------------------------

// manage access to watchlist
// generate ranking using static variables

if( GetOption( "ApplyTo" ) == 2 )
{
    wlnum = GetOption( "FilterIncludeWatchlist" );
    List = CategoryGetSymbols( categoryWatchlist, wlnum ) ;
}
else
    if( GetOption( "ApplyTo" ) == 0 )
    {
        List = CategoryGetSymbols( categoryAll, 0 );
    }
    else
    {
        Error( "The formula works fine if your ApplyTo setting is 'Filter' or 'All' " );
    }

if( Status( "stocknum" ) == 0 ) // GENERATE RANKING WHEN WE ARE ON VERY FIRST SYMBOL
{
    StaticVarRemove( "values*" );
    StaticVarRemove( "rankvalues*" ) ;

    for( n = 0; ( Symbol = StrExtract( List, n ) )  != "";  n++ )
    {
        SetForeign( symbol );
        buysignal = Cross( MACD(), Signal() );
        values = IIf( buysignal, ROC( C, 126 ), -10000 );
        RestorePriceArrays();
        StaticVarSet( "values"  +  symbol, values );
    }

    StaticVarGenerateRanks( "rank", "values", 0, 1224 );
}

symbol = Name();

values = StaticVarGet("values" + symbol ) ;
rank = StaticVarGet( "rankvalues" +  symbol ) ;

PositionScore = values ;


// Output to Exploration -- redundant checks included

Filter = buy ;
AddColumn( PositionScore, "PositionScore" );
AddColumn( values, "values" );
AddColumn( rank, "rank" );
AddColumn( ROC( C, 126 ), "ROC" );
AddColumn( Buy, "Signal Status", format = 1, colorDefault, IIf( Buy, bkcolor = coloryellow, bkcolor = colordefault ) ) ;

SetSortColumns(5);


Try this as a framework instead. I did some simplifying, for example removing your StaticVarGenerateRanks code.

SetOption("usecustombacktestproc", True);

	buysignal = Cross( MACD(), Signal() );
	Buy = buysignal ;
	
	PositionScore = ROC(C,5);
	Sell = Cross(Signal(),  MACD() );
	
	SetPositionSize(5, spsPercentOfEquity);

SetCustomBacktestProc("");
// CBT mid-level 
if( Status( "action" ) == actionPortfolio )
{
	dt = DateTime();
    bo = GetBacktesterObject();
    bo.PreProcess();

	// This logic should not go in the CBT
	//buysignal = Cross( MACD(), Signal() );
	//PositionScore = StaticVarGet( "values" +  symbol );
	
	// Don't check stocknum in CBT. Just delete the old file before diving into the Backtest processing
    filepath = "C:\\Temp\\ScanExportToday.sig";
    fdelete( filepath );
    
    for( bar = 0; bar < BarCount; bar++ )
    {
		Cnt = 0;
		// open file in "share-aware" append mode
		fh = fopen( filepath, "a", True );
		if (fh)
		{
			for( sig = bo.getfirstsignal( bar ); fh AND sig; sig = bo.GetNextSignal( bar ) )
			{
				
					// You can't access your Buy and Sell variables like this in the CBT
					// lastbuyDT =  LastValue( ValueWhen( Buy, DateTime() ) ) ;
				
					//if ((Buy[sig] OR sig.isentry() OR buysignal) AND Cnt <= 10)
					if (sig.isentry() AND Cnt < 10)
					{
						// write to file
						// IIf( buysignal, fputs( "Rank = " + StaticVarGet( "rankvalues" +  symbol ) + " " + symbol + "\n", fh ), fputs( "--" + "\n", fh ) );
						// MR: Don't bother with rank, just output position score
						//fputs( "Rank = " + StaticVarGet( "rankvalues" +  symbol ) + " " + symbol + "\n", fh );
						fputs( NumToStr(dt[bar],formatDateTime) +  " PositionScore = " + sig.PosScore + " " + sig.symbol + "\n", fh );
						Cnt++;
						
					}   
			} // end For all signals
			
			// close file handle
			fclose( fh );

 		}
		else
        {
            _TRACE( "Failed to open the file" );
        }

        bo.ProcessTradeSignals( bar );
	}
        
    bo.PostProcess();

}

3 Likes

@mradtke thanks very much for simplifying this, very much appreciated. I am going to play around with it and use it as a starting framework. I think a lot of my issues (aside from programming experience) is not fully understanding what Amibroker is outputting or perhaps how it fully works internally relative to my expectations or intended output. It's definitely been a learning experience!

I ran the code as is (just changing the location of the output file). I noted that it works when running a back test but does not output when running a scan or exploration.

Is it possible to output the file from the scan or exploration signals that take place on any given day (scan/exploration) while using the positionscore or rank values used in the static variable method to rank them by priority? It sounds very similar but the output appears to be different. I need to be able to generate all the raw signals each day. I promise there is a method to my madness (which I'll reveal soon) that is going to be very cool for Amibroker users including myself. Any further help super appreciated. Thank you.

Regards,

Dave

@DaveDM, the CBT code which is inside the if( Status( "action" ) == actionPortfolio ) block is only executed when you run a back test. The advantage of putting the logic here is that AmiBroker has already ordered everything by PositionScore.

If you still want/need an Exploration, you'll need to add that code back in. You could output your PositionScore as one of the columns, and then use that as one of the columns your sort by using SetSortColumns().

1 Like