Skip to content

Instantly share code, notes, and snippets.

@distagon
Created November 13, 2019 17:21
Show Gist options
  • Save distagon/f4841c936872e839dbed3bf9ebca47a9 to your computer and use it in GitHub Desktop.
Save distagon/f4841c936872e839dbed3bf9ebca47a9 to your computer and use it in GitHub Desktop.
Simple AmiBroker stock market system template. Things to be mindful of when designing your system.
// IMPORTANT! When ready to trade your AB system set the following settings:
// Report > Detailed Log
// Portfolio > Tick "Add artificial future bar"
//
// Set the From-To dates of the Backtest to the current month (eg. 1/Jan/16-1/Feb/16).
// Observe the last row to get your instructions for the next trading day.
// I like placing all variables that can be changed and optimised at the top.
// Then when the time comes to optimise, I create a simple:
// a = Optimize( "a", 1, 1, 10, 1 );
// And then change the reference below to `a`. (eg. maxOpenPositions = a)
maxOpenPositions = 3;
maxOpenLongs = 3;
maxOpenShorts = 0;
minValueTrading = 1000000;
barsValue = 30;
riskPerTrade = 500;
// To minimise "selection bias" whenever the system triggers multiple
// entries, it is best to manually control the maximum number of
// allowable positions and the rank of which stocks are selected according
// to a predetermined score. See `PositionScore` below.
SetOption( "MaxOpenPositions", maxOpenPositions );
SetOption( "MaxOpenLong", maxOpenLongs );
SetOption( "MaxOpenShort", maxOpenShorts );
// Here are some other important SETOPTION variables you might like to add, see here
// for more details: https://www.amibroker.com/guide/afl/setoption.html
SetOption( "InitialEquity", 100000 );
SetOption( "FuturesMode", False ); // stock market trading, if CFD trading set to True
SetOption( "MinShares", 1 ); // minimum quantity of shares that can be bought
SetOption( "MinPosValue", 500 ); // minimum trade size (num of stocks x entry price)
SetOption( "CommissionMode", 0 ); // I prefer using the table as it's easier to enter my stockbroker's charges
SetOption( "AccountMargin", 100 ); // no margin
SetOption( "UsePrevBarEquityForPosSizing", True ); // use previous bar to get equity value - not really needed if using a static $ risk per trade
// These next two options ARE very important. As we do not know when funds will be available
// when we exit a position we need to delay the settlement of these funds at least 1 day.
SetOption( "SettlementDelay", 1 );
SetBacktestMode( backtestRegularRaw );
// Define other system variables here that are needed, but not necessarily
// for optimisation.
i = 2; // initialise numbers
str = 'Hello'; // initialise strings
testArray[0] = Null; // initialise arrays
temp = Null; // initialise a variable with Null value
// The simple loop through all bars in a security. Try not to have too many of these,
// preferably just one. And also try to assign variables to arrays on a per-bar basis.
// And then do your analysis on those arrays in the `IIf` functions outside.
for ( i; i < BarCount; i += 1 ) {
if ( C[i] > C[i-1] ) {
temp = C[i];
}
testArray[i] = temp;
}
// Begin your system by defining entry gates and triggers here. Code your
// operations on the current bar.
// If using PremiumData.Net data and only looking to trade stocks, you may want
// to use the following initialising `IIf` to remove all ETFs, OPTIONS and other
// non-stock securities:
BuySignal = IIf( StrFind( FullName(), " ORDINARY" ) > 0, 1, 0 );
// From here on out simply define your gates and triggers one per line and capture BuySignal:
BuySignal = BuySignal && MA( C * V, barsValue ) > minValueTrading;
...etc...
// We then determine what our price would have been and we need to reference a value that
// would have been determined according to the current bar (our signal bar).
BuyLimitPrice = C;
// Now let's determine our entry price. To prevent another bias creeping into our results
// we've apportioned every trade an equal amount of `riskPerTrade`. We then just need to
// determine what our stop distance will be. Whatever calculations we do here we need to
// again reference the current signal bar, and will likely need to `round` our calculation. An
// example would be something like:
posSize = round( riskPerTrade / ( 2 * ATR( 14 ) ) );
// When you've finished entering your buy signal entry gates and triggers we now need to
// determine whether or not entry has occurred according to HOW you get into the market.
// This will primarily be determined according to what your stockbroker allows for entry.
// Most offer either Limit or Market Orders. Problem with Market Orders is that they
// cannot be created during off-market hours. So most orders would need to be LIMIT if
// you are testing/trading an end-of-day system.
// Therefore, to check if we've had a successful buy entry we need to look forwards, or
// back one bar by making the actual entry bar the current bar.
Buy = Ref( BuySignal, -1 );
// We then determine if the current bar ended up having it's LOW less than our buy limit
// price. Note that we check the `Low` being LESS than our `BuyLimitPrice`. We could check
// if `L <= Ref( BuyLimitPrice, -1 )` but this could give us a false positive, prefer price being
// lower than our entry price to assert that entry would likely have occurred.
Buy = Buy && L < Ref( BuyLimitPrice, -1 );
// If it does we then calculate what the likely entry price would have been, either the
// open of that bar, if price opened below our `BuyLimitPrice` or the `BuyLimitPrice`.
BuyPrice = Min( O, Ref( BuyLimitPrice, -1 ) );
// Then we would use the following declaration to implement the number of shares to purchase:
// (`spsShares` tells AmiBroker how many shares to trade, which `posSize` calculated for us)
// More here: https://www.amibroker.com/guide/afl/setpositionsize.html
// Note also that we are referencing the previous bar (as we are assuming that we are now on
// the entry bar)
SetPositionSize( Ref( posSize, -1 ), spsShares );
// Lastly, and perhaps most importantly prior to entry, we need to tell AmiBroker
// our precedence of entry in to our selected stocks when there is an over-abundance
// of stocks to enter into at the same time.
// Simply reference a calculation with the intent of this calculation producing the
// largest number for stocks you would prefer to get in rather than others.
// Important: reference the signal bar.
posScore = RSI( 100 ); // priority given to stocks with the highest RSI(100) - if you wanted to reference stocks with the lowest RSI(100) you could either go 100-RSI(100) or 1/RSI(100)
// Set your calculated variable to the AmiBroker variable `PositionScore`. Be mindful
// AmiBroker uses absoluted values! Need to be careful of indicators that may give
// negative numbers (eg. MACD).
PositionScore = Ref( posScore, -1 );
// Then we need to determine how we would like to exit our trades. For all LONG trades we have
// Buy for entry and Sell for exit. For all SHORT trades we have Short for entry and Cover for
// exit. Again, we determine our exit by considering our exit on the current bar.
SellSignal = C < Ref( LLV( L, 3 ), -1 );
// Then we determine what our exit price will be, taking into consideration that all values we
// use need to reference the current bar as it assumes we are on the signal bar.
SellLimitPrice = L;
// If you would like to have a target price set this to a value, otherwise set to 0.
SellTargetPrice = HHV( H, 10 );
// Next we check to see whether this value ends up being hit on the next bar.
SellTargetSignal = SellTargetPrice > 0 && H > Ref( SellTargetPrice, -1 );
// Then we once again need to determine what orders our stockbroker takes to exit our trade.
// Assuming that limit sell orders are only possible, we either need to peek forward in our system, OR
// again make the next bar our current bar and look back at the previous bar to determine if
// `SellSignal` occurred. I prefer the latter. Then we check to determine if on our new current bar
// whether the limit price was actually touched.
// We have also added the check to determine whether the limit sell (exit) price was also hit.
Sell = IIf( ( Ref( SellSignal, -1 ) && H > Ref( SellLimitPrice, -1 ) ) || SellSignalTarget, 1, 0 );
// If so, we then set our sell price as either the Opening bar, if price opened higher than
// our `SellLimitPrice`, or the `SellLimitPrice`, or if we were exiting at our `SellTargetPrice`.
SellPrice = Max( O, IIf( SellSignalTarget, Ref( SellTargetPrice, -1 ), Ref( SellLimitPrice, -1 ) ) );
// If we do not wish to receive any further buy signals once we have received our first
// buy signal then we can use the following.
Buy = ExRem( Buy, Sell );
Sell = ExRem( Sell, Buy );
// EXPLORATION
// To successfully monitor when we are to enter our stocks we need to create a filter that
// we run every day to help determine entry. We can run a backtest to help determine when
// we exit.
Filter = Buy;
AddColumn( BuyLimitPrice, "Entry Limit Order", 1.3 );
AddColumn( posSize, "# Shares", 1.0 );
AddColumn( posScore, "Score", 1.3 );
AddColumn( BuyLimitPrice * posSize, "Investment $", 1.2 );
// You can continue to add any more columns of data as needed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment