Unable to add this custom metric to Amibroker backtest report - amibroker

I would like to add an extra column to indicate volatility in the backtest report.
Here is my code. The extra column volatility_recent appears but no value appears in the column. However, if I were to use the commented line trade.AddCustomMetric( "proceeds", trade.Shares*trade.ExitPrice );, some numerical value appears in the column.
What is wrong with the code?
if ( Status( "action" ) == actionPortfolio )
{
bo = GetBacktesterObject();
// run default backtest procedure without generating the trade list
bo.Backtest( True );
volatility_recent = ATR(30);
// iterate through closed trades
for ( trade = bo.GetFirstTrade( ); trade; trade = bo.GetNextTrade( ) )
{
trade.AddCustomMetric( "volatility_recent", volatility_recent );
//trade.AddCustomMetric( "proceeds", trade.Shares*trade.ExitPrice );
}
// iterate through open positions
for ( trade = bo.GetFirstOpenPos( ); trade; trade = bo.GetNextOpenPos( ) )
{
trade.AddCustomMetric( "volatility_recent", volatility_recent );
//trade.AddCustomMetric( "proceeds", trade.Shares*trade.ExitPrice );
}
// generate trade list
bo.ListTrades( );
}

I'm finding it really interesting that you copy text and code solution by others line by line without giving reference.
Your second post here at stackoverflow is line by line copy of responses by Tomasz and me to you at forum.amibroker.com
https://forum.amibroker.com/t/unable-to-add-this-custom-metric-to-backtest-report/7153

Custom metric needs to be scalar (number), not array. ATR(30) is an array. So, use LastValue to get last value of array or Lookup to get the value at specified bar. Pass ATR array of symbol from 1st phase to 2nd phase of backtest via static variables. Then in custom metric line use lookup to extract array element at certain date time (trade.EntryDateTime or trade.ExitDateTime).
StaticVarSet( "CBT_ATR_" + Name(), ATR(30) );
if ( Status( "action" ) == actionPortfolio )
{
bo = GetBacktesterObject();
// run default backtest procedure without generating the trade list
bo.Backtest( True );
// iterate through closed trades
for ( trade = bo.GetFirstTrade( ); trade; trade = bo.GetNextTrade( ) )
{
trade.AddCustomMetric( "volatility_recent", Lookup( StaticVarGet( "CBT_ATR_" + trade.Symbol ), trade.ExitDateTime ) );
//trade.AddCustomMetric( "proceeds", trade.Shares*trade.EntryPrice );
}
// iterate through open positions
for ( trade = bo.GetFirstOpenPos( ); trade; trade = bo.GetNextOpenPos( ) )
{
trade.AddCustomMetric( "volatility_recent", Lookup( StaticVarGet( "CBT_ATR_" + trade.Symbol ), Trade.ExitDateTime ) );
//trade.AddCustomMetric( "proceeds", trade.Shares*trade.EntryPrice );
}
// generate trade list
bo.ListTrades( );
}
EDIT: The credit goes to fxshrat who posted the answer at https://forum.amibroker.com/t/unable-to-add-this-custom-metric-to-backtest-report/7153/2
His answer was posted here and it was rude to post without references. Apologies to fxshrat and Tomasz.

Related

how to find the index of an object with certain property value in an array?

For example, I create a class 'student'
classdef student
properties
name
sex
age
end
methods
function obj = student(name,sex,age)
obj.name = name;
obj.sex = sex;
obj.age = age;
end
end
and then create some objects in an array 'school'
school(1)=student(A,'boy',19)
school(2)=student(B,'girl',18)
school(3)=student(C,'boy',20)
school(4)=student(D,'girl',19)
My question is how to find the index of the objects with certain properties in the array 'school'?
For example, if I want to find students with age 19, the result will be index [1,4]
If I want to find students with age 19 and sex 'boy', the result will be index [1]
Further question 1: how to find the row and colume index? The object with sex 'girl' and age 19 lies in row 1 colume 4.
Further question 2: if school is an cell array, how to solve above problems?
Seems kind of homework questions. However here are the answers:
% find students with age 19,
find ( [school(:).age] == 19 )
% find students with age 19 and sex 'boy',
find ( [school(:).age] == 19 & strcmp( { school(:).sex }, 'boy' ) )
% how to find the row and colume index?
[row, col] = ind2sub( size(school), find ( [school(:).age] == 19 & strcmp( { school(:).sex }, 'girl' ) ) )
Considering the last question, I would convert the cell of school objects back into an array and do as shown above.
If school is a cell array so you have
school = cell(4,1);
school{1}=student(A,'boy',19)
school{2}=student(B,'girl',18)
school{3}=student(C,'boy',20)
school{4}=student(D,'girl',19)
Then you can loop through them to evaluate your conditions. A concise way to do this is with cellfun:
boolAge19 = cellfun( #(x) x.age == 19, school );
idxAge19 = find( boolAge19 );
boolBoy = cellfun( #(x) strcmp( x.sex, 'boy' ), school );
idxBoy = find( boolBoy );
boolBoyAnd19 = boolAge19 & boolBoy;
idxBoyAnd19 = find( boolBoyAnd19 );
You can of course skip intermediate steps, the lines just get dense
idxBoyAnd19 = find( cellfun( #(x) x.age == 19, school ) & ...
cellfun( #(x) strcmp( x.sex, 'boy' ), school ) );

For Flink v 1.10.1 or later how can you programmatically edit the container absolute path in a savepoint metadate file?

I am exploring how to change the absolute path contained in a Flink savepoint's metadata file.
We are looking to migrate a flink stream across AWS regions; however expect to run into problems because of this absolute path. Flink documentation alludes to this problem and suggests using the SavepointV2Serializer to edit the path:
https://ci.apache.org/projects/flink/flink-docs-stable/ops/state/savepoints.html#can-i-move-the-savepoint-files-on-stable-storage
Can anyone help me identify an example that illustrates how to do this? I have not been able to find a reference online.
Also, although looking in the _metadata file, I see an absolute path, I have not found any reference to it in the resulting deserialized objects, nor is it saved to a serialized file.
Thanks in advance for any guidance.
Here's the main file I wrote:
object Main extends App {
val meta = "src" / "main" / "resources" / "_metadata"
println( s"meta: ${meta.path}: ${meta.exists}" )
val contents = meta.contentAsString
println( contents )
// val serde1 = SavepointV1Serializer.INSTANCE
val serde2 = SavepointV2Serializer.INSTANCE
import scala.jdk.CollectionConverters._
val data = meta.inputStream() { in =>
val dis = new java.io.DataInputStream( in )
serde2.deserialize( dis, Thread.currentThread().getContextClassLoader )
}
println( s"META: ${data}" )
println( s"METADATA.version: ${data.getVersion}" )
println( s"METADATA.checkpointId: ${data.getCheckpointId}" )
println( s"METADATA.masterStates: ${Option( data.getMasterStates ).map( _.asScala.mkString( "[", ", ", "]" ) )}" )
println(
s"METADATA.operatorStates: ${Option( data.getOperatorStates ).map( _.asScala.mkString( "[", ", ", "]" ) )}"
)
println( s"METADATA.taskStates: ${Option( data.getTaskStates ).map( _.asScala.mkString( "[", ", ", "]" ) )}" )
val newMeta = "src" / "main" / "resources" / "_NEW_metedata"
val newData = new SavepointV2(
data.getCheckpointId,
Seq.empty[OperatorState].asJava,
data.getMasterStates
)
println( s"NEW_DATA:OpStates: ${newData.getOperatorStates}" )
newMeta.outputStream() { out =>
serde2.serialize( newData, new java.io.DataOutputStream( out ) )
}
}
The underlying issue was actually fixed in Flink 1.11 -- see FLINK-5763 -- savepoints are now relocatable, and no longer contain absolute paths. The only exception seems to be if you use the GenericWriteAheadLog sink.
The documentation needs to be updated, see FLINK-19381.
So if you can upgrade to 1.11.x first, then you should be able to avoid the problem.

Prevent Amibroker from entering the same day as its exit in the same symbol

I am attempting to create a share/stock portfolio based system that will enter at the open and possibly exit on the same day at close if the conditions are met. I have this basicaly working. The thing i cant get going is that I would like my stock system to only ever have 1 open postion in a company at any time.
It seems that if there is both an exit and an entry on the same day, amibroker backtesting is allowing the same company to be purchased on the open, if that same company has a sell order on that same day. Here is an example of this:
Notice at point 1 - we would be entering at the open on the 17th
At point 2, we get a sell signal that day, so we should exit at Close on the 24th.
However at point 3 - we have an entry for the same company on the same day.
To be clear - I would like to allow multiple entries on the same day - this is working. The only thing i would like to figure out is to prevent the backtester from entering the SAME company on the SAME day it exits, as due to the system rules, we would have one day of having 2 positions in the 1 company.
Here is the sample code to replicate this:
SetOption("AllowSameBarExit", True );
SetOption("SettlementDelay", 1 );
Buy = C > MA(C,10);
Sell = C < MA(C,10) OR C > O;
// trade on todays open
SetTradeDelays( 0, 0, 0, 0 );
BuyPrice = Open;
SellPrice = Close;
SetPositionSize( 20, spsPercentOfEquity );
I have read and re-read the page on portfolio timing: here but I still cant figure out how to prevent the entries for the same company on the same day as an exit.
Any help would be greatly appreciated!
UPDATE
It appears that using the OR C > O in the SELL condition is effecting this. If I remove the OR C > O part, I get the correct behaviour. It is entering on the NEXT day. Now Im wondering how to use that exit without reverting back to same bar same company entry and exit...
Thanks to Tomasz from Amibroker for posting the below solution:
SetOption("AllowSameBarExit", True );
BuyPrice = Open;
SellPrice = Close;
Buy = Ref( Close > MA( Close, 10 ), -1 );
Sell = Close > Open OR Close < MA( Close,10);
// removing buys you don't want
intrade = False;
for( i = 0; i < BarCount; i++ )
{
if( NOT intrade )
{
if( Buy[ i ] )
{
intrade = True;
// same bar sell
if( Sell[ i ] ) intrade = False;
}
}
else
{
if( Sell[ i ] )
{
intrade = False;
Buy[ i ] = False; // remove buy if exited this bar
}
}
}
You can find : a detailed discussion here

How to search for a unique sequence in binary data?

I am trying to read a binary file with header. I know certain info is saved after a unique sequence 02 06 08 22 02 02 08 00. How could I find the position of such unique sequence?
I can use
String StreamReadAsText( ScriptObject stream, Number encoding, Number count )
to read the binary file one by one. But I guess it is pretty silly and slow.
Besides, how do i compare the result from StreamReadAsText() when the output is not a actual text (between 00 and 1F in the Ascii Table)?
Then, How do i read the binary file as int8 (the same size as a character in a string).for example, read 02, then 06, then 08 etc...
Any help is welcome and appreciated.
Regards,
Roger
You are already on the right track with reading the file with the streaming commands. However, why would you want to read the stream as text? You can read the stream as any (supported) number, using the tagGroup object as a proxy with TagGroupReadTagDataFromStream().
There is actually an example in the F1 help-section where the streaming commands are listed, which I'm just copying here.
Object stream = NewStreamFromBuffer( NewMemoryBuffer( 256 ) )
TagGroup tg = NewTagGroup();
Number stream_byte_order = 1; // 1 == bigendian, 2 == littleendian
Number v_uint32_0, v_uint32_1, v_sint32_0, v_uint16_0, v_uint16_1
// Create the tags and initialize with default values
tg.TagGroupSetTagAsUInt32( "UInt32_0", 0 )
tg.TagGroupSetTagAsUInt32( "UInt32_1", 0 )
tg.TagGroupSetTagAsLong( "SInt32_0", 0 )
tg.TagGroupSetTagAsUInt16( "UInt16_0", 0 )
tg.TagGroupSetTagAsUInt16( "UInt16_1", 0 )
// Stream the data into the tags
TagGroupReadTagDataFromStream( tg, "UInt32_0", stream, stream_byte_order );
TagGroupReadTagDataFromStream( tg, "UInt32_1", stream, stream_byte_order );
TagGroupReadTagDataFromStream( tg, "SInt32_0", stream, stream_byte_order );
TagGroupReadTagDataFromStream( tg, "UInt16_0", stream, stream_byte_order );
TagGroupReadTagDataFromStream( tg, "UInt16_1", stream, stream_byte_order );
// Show the taggroup, if you want
// tg.TagGroupOpenBrowserWindow("AuxTags",0)
// Get the data from the tags
tg.TagGroupGetTagAsUInt32( "UInt32_0", v_uint32_0 )
tg.TagGroupGetTagAsUInt32( "UInt32_1", v_uint32_1 )
tg.TagGroupGetTagAsLong( "Sint32_0", v_sint32_0 )
tg.TagGroupGetTagAsUInt16( "UInt16_0", v_uint16_0 )
tg.TagGroupGetTagAsUInt16( "UInt16_1", v_uint16_1 )
There is already a post here on site about searching for a pattern within a stream: Find a pattern image (binary file)
This shows how you would use a stream to look in an image, but you can use the filestream directly of course.
As an alternative, you can read a whole array from the stream with ImageReadImageDataFromStream after preparing a suitable image beforehand.
You can then use images to search location. This would be an example:
// Example of reading the first X bytes of a file
// as uInt16 data
image ReadHeaderAsUint16( string filepath, number nBytes )
{
number kEndianness = 0 // Default byte order of the current platform
if ( !DoesFileExist( filePath ) )
Throw( "File '" + filePath + "' not found." )
number fileID = OpenFileForReading( filePath )
object fStream = NewStreamFromFileReference( fileID, 1 )
if ( nBytes > fStream.StreamGetSize() )
Throw( "File '" + filePath + "' has less than " + nBytes + "bytes." )
image buff := IntegerImage( "Header", 2, 0, nBytes/2 ) // UINT16 array of suitable size
ImageReadImageDataFromStream( buff, fStream, kEndianness )
return buff
}
number FindSignature( image header, image search )
{
// 1D images only
if ( ( header.ImageGetNumDimensions() != 1 ) \
|| ( search.ImageGetNumDimensions() != 1 ) )
Throw( "Only 1D images supported" )
number sx = search.ImageGetDimensionSize( 0 )
number hx = header.ImageGetDimensionSize( 0 )
if ( hx < sx )
return -1
// Create a mask of possible start locations
number startV = search.getPixel( 0, 0 )
image mask = (header == startV) ? 1 : 0
// Search all the occurances from the first
number mx, my
while( max( mask, mx, my ) )
{
if ( 0 == sum( header[0,mx,1,mx+sx] - search ) )
return mx
else
mask.SetPixel( mx, 0, 0)
}
return -1
}
// Example
// 1) Load file header as image (up to the size you want )
string path = GetApplicationDirectory( "open_save", 0 )
number maxHeaderSize = 200
if ( !OpenDialog( NULL, "Select file to open", path, path ) ) Exit(0)
image headerImg := ReadHeaderAsUint16( path, maxHeaderSize )
headerImg.ShowImage()
// 2) define search-header as image
image search := [8]: { 02, 06, 08, 22, 02, 02, 08, 00 }
// MatrixPrint( search )
// 3) search for it in the header
number foundAt = FindSignature( headerImg, search )
if ( -1 == foundAt )
Throw( "The file header does not contain the search pattern." )
else
OKDialog( "Found the search pattern at offset: " + foundAt * 16 + "bytes" )
If you're on a modern machine, just load the file into memory, then scan for the sequence using a memory comparison function and a travelling index.
It's not the most memory efficient way of doing things or even the fastest, but it's easy and fast enough, assuming you have resources to burn.

Shiny assign reactive variable from input using loop

I'm trying to assign reactive variable based on my input using loop. For example, I want to select the column (from input) in iris data set. Then get the unique value from that column. And I want to do this in the loop. I find it works for my 'joke' variable, but not for my 'Group[[paste0('Gcol',i)]]' variable. I've been searching answer for this for days.
Thank you for your help in advance!
library(shiny)
data=iris
ui <- fluidPage(
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
sidebarPanel(
fluidRow(
column(9,wellPanel(lapply(1:5, function(i) {
selectizeInput(paste0('GroupVar',i), paste0('Group ',i), choices = sort(colnames(data)),
options = list(placeholder = 'Select one',
onInitialize = I('function() {
this.setValue(""); }')))
})
)))
),
mainPanel(
fluidRow(column(6, wellPanel(
lapply(1:5, function(i) {
uiOutput(paste0('GroupOpt', i))
})
))),
textOutput("try4"),
textOutput("try2"),
textOutput("try21"),
textOutput("try3"),
textOutput("try")
)
)
)
server <- function(input, output) {
Group=reactiveValues()
for (i in 1:5){
Group[[paste0('Gcol',i)]]=reactive({
data[,which(colnames(data)==
input[[paste0('GroupVar',i)]])]})
}
joke=reactive({data[,which(colnames(data)==input[[paste0('GroupVar',1)]])]})
lapply(1:5, function(i) { output[[paste0('GroupOpt',i)]] = renderUI({
selectInput(paste0("GroupOpt",i), "Select group",multiple=TRUE,
sort(as.character(unique(Group[[paste0('Gcol',i)]])))
)
})})
output$try4 = renderText({print(paste0('it
is',input[[paste0('GroupVar',1)]]))})
output$try2 = renderText({print(dim( Group[[paste0('Gcol',1)]]()))})
output$try21 = renderText({print(class( Group[[paste0('Gcol',1)]]()))})
output$try3 =
renderText({print(which(colnames(data)==input[[paste0('GroupVar',1)]]))})
output$try = renderText({print(unique(as.character(joke())))})
}
# Run the application
shinyApp(ui = ui, server = server)
data[, which(colnames(data)=="Species")] is not a dataframe, this is the column Species, a factor. If you want to allow a one-column dataframe, do data[, which(colnames(data)=="Species"), drop=FALSE]
Replace your loop with the following one, and your app works (but maybe not as you expect; I'm not sure to understand what you want).
for (i in 1:5){
local({
ii <- i
Group[[paste0('Gcol',ii)]]=reactive({
data[,which(colnames(data)==input[[paste0('GroupVar',ii)]])]})
})
}

Resources