Intro
One of the most powerful tools retail traders have is access to a charting platform as open ended and robust as TradingView. TradingView’s scripting environment allows users of all skill levels to interact with their charts and analysis in utterly unique and powerful ways. One of the top questions I hear is “How to create a trading bot in TradingView?” or “How do I automate trades from TradingView to MT4?”. Using a basic example, I will guide you through the process of creating an automated trading strategy using PineScript which will follow an Entry and Exit Algorithm and will execute trade orders to MT4 via TradingView’s alert webhook integration.
As we go through this process it is vital to keep some main points in mind regarding trade automation:
-
Start Simple: Begin with a simple strategy. Complex strategies can be more challenging to automate and troubleshoot.
-
Understand the Risks: Be aware of the risks involved in automated trading, such as technical failures or unexpected market events.
-
Regular Updates: Keep your strategy updated with changing market conditions. What works today may not work tomorrow.
-
Risk Management: Always incorporate risk management rules into your automated strategy to protect your capital.
Why use ControlTower? I developed ControlTower because I rely on trading for monthly income and I want control of my system. When I started using automation to assist my trading the service I was using was kind of janky. I had to always have TradingView open on my terminal as well as use a Chrome browser extension. Glitches or forgetfulness would lead to TradingView not refreshing in the browser or perhaps the browser not being open… but I worked through that and had success using the system. But it was cumbersome. Then, without warning, the service went down. The developer was non responsive and suddenly the entire trading system I was reliant on no longer worked! I had to revert to manually managing trades but I was no longer really set up to do so and the whole experience was a major disruption to my trading work. After that I vowed to be as minimally reliant on 3rd parties for my trading as possible. I developed this software to be as stand alone, efficient and easy to use as possible. ControlTower runs on your computer so you are not reliant on a 3rd party to host it and because it runs locally it is more secure. And, perhaps critically, it is blazing fast executing trades in less than 1 microsecond.
The Plan
Before we dive into it, there are three main components to our mission:
- Identify a strategy we want to trade
- Code the strategy
- Identify entry triggers
- Identify exit triggers
- Include risk management via stop loss
- Add automation via TradingView alerts and connect to MT4 using Control Tower
Is Automation Right For Your Strategy?
Automation is not always the best way to effectively trade. Depending on your strategy and style it may or may not be the right choice. Some reasons to use automation include:
- High-Frequency Strategies: If your strategy requires executing a large number of trades in a short time, automation is essential.
- Strategies Based on Precise Timing: Automation ensures that your trades are executed when your conditions are met, which is crucial for strategies dependent on timing.
- Reducing Emotional Trading: Automated trading helps maintain discipline, as the trades are executed based on predefined criteria, removing emotional biases.
- Scalability: Automated systems can manage multiple positions across various markets simultaneously, which would be highly challenging to do manually.
- Time: If your system is highly rules-based then why not have a bot trade it for you so you can enjoy life’s most precious resource: time.
For our simple example, ease of execution, reducing emotion and time are great reasons to create a bot.
Identify the Strategy
This is the hardest part of this process and obviously there are many, many strategies you could choose. Strategy selection is beyond the scope of this article however key elements to look for are:
-edges
-repeatability
-a philosophy we understand and believe
For our purposes we will choose a very simple, yet effective moving average strategy. The main concept here is that trending markets continue to trend until they don’t. We want a way to identify when a trend begins (and enter) and when a trend has collapsed (and then exit). Using a short and a long moving average crossover is a classic method of identifying a trend. We will use the crossovers of short over long (either up or down) to trigger our entry (either long or short). The theory here is that the long moving average is acting as a sort of base line price – it is literally the average price over a given period of time. We can always expect the price to return to the average. I can’t reiterate that enough. It is an average and that means the price will always meet it (or it will meet the price). If the price is above that average then we would say the price action is bullish… but candles are very short lived so we will modify our definition of bullish by comparing two moving averages of different durations. If there is an average which is above a longer average, then the price is clearly in a state of consistently being above the long term average. This is what we’d call a trend and it is psychologically very powerful for traders and markets. We want to ride the wave of the trend until it is time to get off.
A note on moving averages: we will incorporate the ability to user-adjust the moving average selection, however, from years of back testing and trading experience I will jump start the selection process by saying that the 8EMA and the 21EMA are very powerful moving averages, particularly the 21EMA. The 21EMA will serve as our long/slow average and the 8EMA will be our short/fast moving average. The 21EMA is gold and I personally rely heavily on it in my own personal trading strategy.
In conclusion our Entry Algorithm will be when a short moving average crosses over a longer moving average.
For our Exit Algorithm we could use when the short moving average crosses back against the longer average but let’s make it more interesting and allow for some pullbacks in our strategy. Let’s use an even longer moving average as the exit trigger. If the price (not moving average) crosses this long moving average (the 200SMA for example) then we will exit the trade under the belief that now the trend is ending.
Code The Strategy
To begin let’s identify our key features. We need:
-user adjustable moving averages for the fast and slow entry averages, including the type of moving average
-user adjustable moving average for the exit trigger, including the type of moving average
-a function for applying the type of moving average to each of the 3 averages in our script
-a way of identifying when the crossovers occur
-triggering entry or exit
-assigning an alert to each trigger
-plot each moving average
-plot entry and exit points
User Inputted Moving Averages
To begin we will need an integer input value for each of the moving averages – this will allow the user to adjust the moving average on the chart and play with the elements of their system. I am using values which I think will work well for the default values.
periodShort = input.int(8, minval=1, title='Short Moving Average Length', group="Short Moving Average Selection",inline="shortMA", tooltip="Select the length and the type of moving average to use for the Short moving average.") periodLong = input.int(21, minval=1, title='Long Moving Average Length', group="Long Moving Average Selection",inline="longMA", tooltip="Select the length and the type of moving average to use for the Long moving average.") periodExit = input.int(200, minval=1, title='Exit Moving Average Length', group="Exit Moving Average Selection",inline="exitMA", tooltip="Select the length and the type of moving average to use for the Exit moving average.")
So far so good. Next we need to know which kind of smoothing we want to apply to each moving average. Here we are just going to let the user select from a list of strings and assign the string to a variable. Later we will build a function to calculate the moving average and we will use the variable to change the calculation type.
smoothingShort = input.string(title='Choose Smoothing Type for Short Moving Average', defval='EMA', options=['RMA', 'SMA', 'EMA', 'WMA', 'VWMA', 'SMMA', 'DEMA', 'TEMA', 'HullMA', 'LSMA'])
smoothingLong = input.string(title='Choose Smoothing Type for Long Moving Average', defval='EMA', options=['RMA', 'SMA', 'EMA', 'WMA', 'VWMA', 'SMMA', 'DEMA', 'TEMA', 'HullMA', 'LSMA'])
smoothingExit = input.string(title='Choose Smoothing Type for Exit Moving Average', defval='EMA', options=['RMA', 'SMA', 'EMA', 'WMA', 'VWMA', 'SMMA', 'DEMA', 'TEMA', 'HullMA', 'LSMA'])
Now let’s build the aforementioned function which will do our moving average calculation. This function will receive two arguments, both of which come from the user input section we just added, namely the period and the smoothing type. PineScript has built-in moving average functions for each smoothing type so all we’re really doing here is saying “if the user chose EMA for the smoothing type, then use the built-in PineScript function ta.ema ” and use the period selected by the user.
//MOVING AVERAGE FUNCTION
//Moving Average Calculation for each type of moving average
ma(smoothing, period) =>
if smoothing == 'RMA'
ta.rma(close, period)
else
if smoothing == 'SMA'
ta.sma(close, period)
else
if smoothing == 'EMA'
ta.ema(close, period)
else
if smoothing == 'WMA'
ta.wma(close, period)
else
if smoothing == 'VWMA'
ta.vwma(close, period)
else
if smoothing == 'SMMA'
na(close[1]) ? ta.sma(close, period) : (close[1] * (period - 1) + close) / period
else
if smoothing == 'HullMA'
ta.wma(2 * ta.wma(close, period / 2) - ta.wma(close, period), math.round(math.sqrt(period)))
Next we need to use the function. For each moving average we will create a variable and assign the value of that variable the result of the function calculation. So for the short moving average, we send the user-selected values for the short moving average to the function and likewise for each moving average we want to calculate:
//ASSIGN A MOVING AVERAGE RESULT TO A VARIABLE
shortMA = ma(smoothingShort, periodShort)
longMA = ma(smoothingLong, periodLong)
exitMA = ma(smoothingExit, periodExit)
longMA = ma(smoothingLong, periodLong)exitMA = ma(smoothingExit, periodExit)
Great! Now we’ve got moving averages calculated for each of the 3 our strategy requires which the user can modify on the chart. But how do we see them?
PLOT
//PLOT THOSE VARIABLES
plotTakeProfitColor = time_cond ? color.green : na //only assign a color if within time parameters
plot(shortMA, linewidth=4, color=color.new(color.yellow, 0), title=’The Short Moving Average’)
plot(longMA, linewidth=4, color=color.new(color.blue, 0), title=’The Long Moving Average’)
plot(exitMA, linewidth=1, color=color.new(color.red, 0), title=’The Exit Moving Average’)
plot(takeProfit,linewidth=4,color=plotTakeProfitColor,title=”Take Profit Price”, style=plot.style_line) //note this will only plot with a color if within the time range
We can now see our 3 moving averages on the chart. You’ll notice I added in a conditional test based on time – this will be discussed later in this tutorial but have added it here for convenience.
THE ALGORITHM
So far we’ve got our main elements for our automated trading system in place and visible on the chart. Now for the part where the bot needs to identify the entry and exit triggers.
Enter ta.crossover and ta.crossunder
As we initially said, our system will enter a long trade when the short moving average crosses over the long and a short when the short moving average crosses under the longer moving average. PineScript has a handy built-in function for determining crosses: ta.crossover and ta.crossunder
Ta.crossover/under is quite simple: it takes 2 arguments and when the first crosses above the second it returns TRUE, otherwise it returns FALSE. We will use this output to determine if the cross has happened and then place an order if TRUE. We will use the ever handy ternary operator :
Key to understanding all of this is to remember that PineScript runs the code anew for each new bar on the chart. Thus this code runs in a loop by design. Each time a new bar is printed the script will run and will test whether shortMA has crossed longMA and will have a result of TRUE or FALSE for this test. If the current bar happens to be TRUE then an alert() will trigger. If current bar’s cross test is FALSE then no alert() will be triggered.
buy = ta.crossover(shortMA, longMA) ? true : nasell = ta.crossunder(shortMA, longMA) ? true :na
This says: the value of the variable buy/sell is the result of the following test: if ta.crossover()/ta.crossunder() returns TRUE then the boolean TRUE is the result and assigned to buy/sell. Otherwise the result is ‘na’ (aka empty). It’s a nice short version of writing out a longer if then statement.
Next we will create a variable and assign it a string based on whether buy or sell are true. We will be using this string as a convenience later when we set up our automation alerts. Again we’re using the ternary operator, this time nesting two together. It translates to this:
If buy is true, then the variable action is assigned the string “LongEntry”. However if buy is not true then check if the value of sell is true and, if so, assign the string “ShortEntry” to the variable action. If buy is false and sell is false then assign no value to the variable using na
action = buy ? "LongEntry" : sell ? "ShortEntry" : na
That’s it! Now, when we send the alert, the correct syntax for MT4 will be included.
For determing when to exit we will use a similar technique:
exitLong = ta.crossunder(close, exitMA) ? true : naexitShort = ta.crossover(close, exitMA) ? true : na
Our trade message from TradingView to MT4 will require correct syntax to execute the order. As we did above for the variable action, we will test the values of exitLong and exitShort and then assign a string based on that test:
exitString = exitLong ? "LongExit" : exitShort ? "ShortExit" : na
Risk Management
At this point in our script we have created a system which enters a trade either long or short and which will close an open trade as well. Technically because our algorithm includes an exit parameter we have risk management in place. It’s not the greatest risk management because it is dynamic but it suits this simple system. Later we could add a stop loss and, as the trade progresses, have it trail in order to try to maximize our profits. We will cover this is a future article.
Adding In Time Parameters
When testing it would be nice to be able to control the time period of the testing – results from 2020 could be quite different than from 2024 or compared to all time. We can add user inputs to our parameters section to define a start and end date and then use those in our testing logic.
The inputs would look like this
// BACKTESTING RANGE
startDate = input.time (timestamp(“01 Jan 2024″), ”, group=’Backtesting Date Range’, inline=’Start Period’, tooltip=”Select the start and end dates and times for the testing range. The strategy will only be tested within these dates.”)
finishDate = input.time (timestamp(“01 Jan 2025″), ”, group=’Backtesting Date Range’, inline=’Start Period’)
And then we will create a variable time_cond to evaluate if the current time of the bar is within these two date ranges. If it is time_cond will be TRUE.
time_cond = time >= startDate and time <= finishDate //we will use time_cond later to test if the current candle is within the selected dates by seeing if time_cond is TRUE
As part of our test of whether or not to create an entry, we will first test if time_cond is TRUE. If it is the script will continue to the next phase and test if action has been assigned LongEntry or ShortEntry, or if exitLong or exitShort are set to TRUE.
The Strategy Parameters
We’ve now got a fully functioning system written! Next we just need to add in the strategy parameters for TradingView and we can test the results of our automated trading strategy. Within TradingView we can simulate/backtest our strategy and, once we’re happy, we can then modify the script to turn it into a full fledged automated TradingView to MT4 trading bot!
We will first start by testing if time_cond is true. This will limit the scope of the testing to the time parameters inputted by the user. If time_cond is true then we will test our previous variables to see if we are entering Long or Short, or if we are needing to exit a previously entered trade.
if time_cond
if action == "LongEntry"
strategy.entry('long', strategy.long, 1000)
if action == "ShortEntry"
strategy.entry('short', strategy.short, 1000)
if exitLong
strategy.close('long')
if exit
Shortstrategy.close('short')
Let’s Run The Strategy
Running this strategy on the 240 chart between 2022 and 2024 doesn’t give great results but it is profitable.
We now have a complete strategy which we can play with. Below is the complete code we have written so far:
//@version=5
indicator('EMA Crossover Strategy', overlay=true)
// BACKTESTING RANGE
startDate = input.time (timestamp("01 Jan 2024"), '', group='Backtesting Date Range', inline='Start Period', tooltip="Select the start and end dates and times for the testing range. The strategy will only be tested within these dates.")
finishDate = input.time (timestamp("01 Jan 2025"), '', group='Backtesting Date Range', inline='Start Period')
time_cond = time >= startDate and time <= finishDate //we will use time_cond later to test if the current candle is within the selected dates by seeing if time_cond is TRUE
//CREATE USER-INPUT VARIABLES
periodShort = input.int(8, minval=1, title='Short Moving Average Length', group="Short Moving Average Selection",inline="shortMA", tooltip="Select the length and the type of moving average to use for the Short moving average.")
smoothingShort = input.string(title='Type', defval='EMA', options=['RMA', 'SMA', 'EMA', 'WMA', 'VWMA', 'SMMA', 'DEMA', 'TEMA', 'HullMA', 'LSMA'],inline="shortMA", group="Short Moving Average Selection")
periodLong = input.int(21, minval=1, title='Long Moving Average Length', group="Long Moving Average Selection",inline="longMA", tooltip="Select the length and the type of moving average to use for the Long moving average.")
smoothingLong = input.string(title='Type', defval='EMA', options=['RMA', 'SMA', 'EMA', 'WMA', 'VWMA', 'SMMA', 'DEMA', 'TEMA', 'HullMA', 'LSMA'], group="Long Moving Average Selection",inline="longMA")
periodExit = input.int(200, minval=1, title='Exit Moving Average Length', group="Exit Moving Average Selection",inline="exitMA", tooltip="Select the length and the type of moving average to use for the Exit moving average.")
smoothingExit = input.string(title='Type', defval='EMA', options=['RMA', 'SMA', 'EMA', 'WMA', 'VWMA', 'SMMA', 'DEMA', 'TEMA', 'HullMA', 'LSMA'], group="Exit Moving Average Selection",inline="exitMA")
//MOVING AVERAGE FUNCTION
//Moving Average Calculation for each type of moving average
ma(smoothing, period) =>
if smoothing == 'RMA'
ta.rma(close, period)
else
if smoothing == 'SMA'
ta.sma(close, period)
else
if smoothing == 'EMA'
ta.ema(close, period)
else
if smoothing == 'WMA'
ta.wma(close, period)
else
if smoothing == 'VWMA'
ta.vwma(close, period)
else
if smoothing == 'SMMA'
na(close[1]) ? ta.sma(close, period) : (close[1] * (period - 1) + close) / period
else
if smoothing == 'HullMA'
ta.wma(2 * ta.wma(close, period / 2) - ta.wma(close, period), math.round(math.sqrt(period)))
//ASSIGN A MOVING AVERAGE RESULT TO A VARIABLE
shortMA = ma(smoothingShort, periodShort)
longMA = ma(smoothingLong, periodLong)
exitMA = ma(smoothingExit, periodExit)
//PLOT THOSE VARIABLES
plotTakeProfitColor = time_cond ? color.green : na //only assign a color if within time parameters
plot(shortMA, linewidth=4, color=color.new(color.yellow, 0), title='The Short Moving Average')
plot(longMA, linewidth=4, color=color.new(color.blue, 0), title='The Long Moving Average')
plot(exitMA, linewidth=1, color=color.new(color.red, 0), title='The Exit Moving Average')
plot(takeProfit,linewidth=4,color=plotTakeProfitColor,title="Take Profit Price", style=plot.style_line) //note this will only plot with a color if within the time range
//ENTRY LOGIC
buy = ta.crossover(shortMA, longMA) ? true : na
sell = ta.crossunder(shortMA, longMA) ? true :na
action = buy ? "LongEntry" : sell ? "ShortEntry" : na //assign strings for use later with MT4 and Control Tower integration
//EXIT LOGIC
exitLong = ta.crossunder(close, exitMA) ? true : na
exitShort = ta.crossover(close, exitMA) ? true : na
exitString = exitLong ? "LongExit" : exitShort ? "ShortExit" : na //assign strings for use later with MT4 and Control Tower integration
//ALERT CALLS WHICH SEND TRADE ORDER DATA TO MT4 VIA CONTROL TOWER
if time_cond //test if current candle is within the time parameters
if action == "LongEntry"
strategy.entry('long', strategy.long, 1000)
if action == "ShortEntry"
strategy.entry('short', strategy.short, 1000)
if exitLong
strategy.close('long')
if exitShort
strategy.close('short')
In the next article we will setup the script to connect TradingView to MT4 via alerts and Control Tower.