How To Create A Simple Trading Strategy With TradingView

QS
16 min readNov 17, 2019

--

Most important thing is feeling inspired and relaxed. Forcing your way into anything under stress and pressure will only end up in disaster. I’m a perfectionist by nature, yet the best things I have created have come from living the moment. Today I will walk you through my approach to create a trading system. I ask the reader to not focus on the possible mistakes I would make but to observe closely my way of thinking and hopefully learn something which is the purpose of this post.

Let’s dive into the process, first you must have an account with TradingView. Then create a new chart layout. On this project I will use the “2H” timeframe and the cryptocurrency pair “BITMEX:XBTUSD”.

There are many ways to create a strategy, mine starts with a logical idea. In this case I remember reading an article once that talked about the market trending up when the price was above the VWAP and the opposite when it was under. For more information on VWAP, click here.

If you have tried to code a strategy you read online, you would know most of time (if not all), the backtest looks disappointing. However, everything you read is stored in our brains and one day one of those things becomes useful. Then add coding what you read and that reinforces the knowledge acquired. The more you research the more your mind start connecting the dots of all you have processed, and then original ideas start flowing. This is the reward of long hours trying to create the ultimate strategy.

Once we have an idea, then we open the “Pine Editor”. In my case I remembered it was a term that had the letter “w” and consisted of 4 letters and that was it, it was a very vague idea and finding the solution became very interesting.

One of TradingView best features is its “Pine Script Language Reference Manual”, if you hover over any of its operators, or built-in variables/functions; in this case, the word “close” which hold the current closing price of a security for the specified candle or bar, then you can see which keys to press to open it based on your operating system.

There you can search for a specific term or read through it all and experiment. On this occasion I was searching for “w”, while on it I found “SWMA”. After reading what it was I knew it wasn’t the one I was looking for, but since it uses a short look back, length, or period (the amount of bars you want the indicator to look into the past, the higher the less noise but also more lag) I thought I would give it a try. Once coded, it should look like below.

//@version=4
study("My Script")
swmaClose = swma(close)plot(close)

Notice I only created a variable called “swmaClose” and gave the value of “swma(close)”. For those who are new to programming, I think one of the scariest things is how overwhelming coding is from the bigger picture which is when all the code is done and there are lots and lots of lines. One thing I have learned is that no matter how complex a program is, it all starts with a single line of code. So when it comes to programming, all you got to focus on is the line you are staring at.

To observe what the code does in the chart, click on “Add to Chart”.

There you will see what the code above does.

Not exactly what we had in mind, what we want the code to do at this time is to print out “swmaClose” near the price. To do that, we must add a few changes. Remember the “Pine Script Language Reference Manual”?

To make the script be shown near the price we must change the value of “overlay” to the value of “true”.

study(“My Script”, overlay = true)

To make the script output the value of “swmaClose”, we must replace “close” with it on the “plot()” function.

plot(swmaClose)

Now our code looks like this.

//@version=4
study(“My Script”, overlay = true)
swmaClose = swma(close)plot(swmaClose)

So we managed to achieve our initial goal, however, we notice we can barely see the value of “swmaClose”. If we take a look at “plot()” we can find clues to why.

So we can change the “color”, “linewidth”, and “transp” from the “Pine Editor” and we will get a better view of the plot.

plot(swmaClose, color = color.red, linewidth = 4, transp = 0)

Despite the change we still need to zoom in to be able to see it more clearly, this is because SWMA uses only the last four bars on its calculation and doesn’t lag too much behind the actual price, as a result it follows the price very close.

There you can see that when the price crosses up SWMA then it continue that direction until you crosses down. So following this example you can build any logic, pass it to the variable and see how it compares to the price, or you can do it another way, using colors, which is how I personally do it and I find it more visually appealing.

Before I show you that, let’s add the actual variable I wanted at the beginning of this idea. Where do we go to find it?

Let’s declare “vwapClose” and assign the value of “vwap(close)”.

vwapClose = vwap(close)

Notice the main “VWAP” uses “HLC3” (high, low, and close divided by three). I chose “close” as sources series because it lags less.

Let’s now dive into the colors approach, I have created two new variables, “swmaLong” and “vwapLong”. Both compares the price (“close”) with their value, the idea is when the price is above each of them then it will trend up.

swmaLong = close > swmaClose
vwapLong = close > vwapClose

To prove the theory, let’s use “bgcolor()” function, to remember what it means try thinking of it as the background color.

What we want is if the condition is true, then show X color, otherwise Y color.

bgcolor(swmaLong ? color.blue : na)
bgcolor(vwapLong ? color.orange : na)

To understand more about the ternary conditional operator (?:)

So after removing the “plot()” function, adding the new variables “…Long”, and adding “bgcolor()”. Our script should look like this so far.

//@version=4
study(“My Script”, overlay = true)
swmaClose = swma(close)
vwapClose = vwap(close)
swmaLong = close > swmaClose
vwapLong = close > vwapClose
bgcolor(swmaLong ? color.blue : na)
bgcolor(vwapLong ? color.orange : na)

Not exactly what I had in mind, to better understand the colors we need to separate them, the best way to do that is by creating two separate scripts.

One will show Blue when “swmaLong” is true, and the other Orange when “vwapLong” is true. So we don’t need to delete any code, we can just comment the line out like below. This will make the script skip that line and not run it.

Despite having both scripts broken into two, we still have the same issue because they overlap with each other.

The solution is changing “overlay” to “false” in both of them.

Now you can find patterns between the colors and the price going up or down. Remember at the start of this article I mentioned I used a logical way to create trading algorithms. In other words, I search for patterns that makes sense. Other ways such as trying to find the right setting out of thousands or millions of combination leads to what is known as overfitting.

In this example regarding the patterns I found, these are three simple scenarios.

A: Blue/Orange happening together at the same time.
B: Orange happening only.
C: Orange happening first, then blue after.

I decided to go with scenario “C”. The conditions so far:
1- Orange must happen in the bar but not Blue.
2- When (1) takes place, neither Orange or Blue must occur in the bar before.

I follow by then declaring a variable called “triggerLong” and assign conditions (1) and (2).

triggerLong = vwapLong and not vwapLong[1] and not swmaLong and not swmaLong[1]

Next we check that it works correctly and get a clearer idea if our patterns are on point by passing this value to a new “bgcolor()” function.

bgcolor(triggerLong ? color.purple : na)

Before we continue, I want to touch the topic of how many indicators we should use. To make it easier, let’s call this the “Rule of Never”. Never uses only one, never use too many. One is bad because we always need a confirmation, and too many because the more indicators or logic you use the more strict/rigid your system becomes, the result of such is a shorter life span. A versatile system is flexible which allows it to be resilient to market changes. Personally I go with two or three indicators.

Since we want our algorithm to depend on two signals, we want a variable that can save its state while it waits for the other and final condition to occur.

So we need to create a new variable that can save the state to true when “triggerLong” or Purple happens. We shall name it “saveLong”.

saveLong = false, saveLong := triggerLong ? true : not vwapLong ? false : saveLong[1]

What “saveLong” does is when Purple occur, it becomes “true” while Orange still happening, but the moment Orange doesn’t show, it changes to “false”.

Let’s now observe what “saveLong” does in the chart. Below is how our code should look so far.

//@version=4
study(“My Script”, overlay = false)
swmaClose = swma(close)
vwapClose = vwap(close)
swmaLong = close > swmaClose
vwapLong = close > vwapClose
triggerLong = vwapLong and not vwapLong[1] and not swmaLong and not swmaLong[1]
saveLong = false, saveLong := triggerLong ? true : not vwapLong ? false : saveLong[1]
// bgcolor(swmaLong ? color.blue : na)
// bgcolor(vwapLong ? color.orange : na)
// bgcolor(triggerLong ? color.purple : na)
bgcolor(saveLong ? color.yellow : na)

Notice we added a new “bgcolor()” and passed the value of “saveLong”, so when it is true, the color Yellow would show in the chart.

Our long entry signal is almost done, so far we have been working with Orange, and special conditions around it coded into Purple. The remaining piece of the puzzle is adding Blue to it. So we create another variable and name it “startLong”.

startLong = saveLong and swmaLong

What “startLong” does is while the state of Purple saved in Yellow is true and Blue happens; then it finally send a signal to open a long position.

Let’s make our final “bgcolor()” and assign the value of “startLong”.

//@version=4
study(“My Script”, overlay = false)
swmaClose = swma(close)
vwapClose = vwap(close)
swmaLong = close > swmaClose
vwapLong = close > vwapClose
triggerLong = vwapLong and not vwapLong[1] and not swmaLong and not swmaLong[1]
saveLong = false, saveLong := triggerLong ? true : not vwapLong ? false : saveLong[1]
startLong = saveLong and swmaLong// bgcolor(swmaLong ? color.blue : na)
// bgcolor(vwapLong ? color.orange : na)
// bgcolor(triggerLong ? color.purple : na)
// bgcolor(saveLong ? color.yellow : na)
bgcolor(startLong ? color.green : na)

Can you guess what color would it be when “startLong” is true?

Our chart shall now looks like this.

Notice how much noise is cut down with each new line of code added.

So far you have learned how to use colors to express conditions of trading signals. Now that we have finished with our entry signal, we can get rid of the other colors, leaving only the Green one. Since we are only using only one color now, we don’t need the script in the bottom anymore, so the new look should be like below.

The next part is how to backtest what we have built. Remember our script on the second line started with study(…). Well, TradingView offers two types of scripts, study and strategy. They have many differences, but the main one is that you can create alerts under a study and you can backtest under the other one. There are certain parameters that only work under a strategy, but all study features work with the former. So to convert a study into a strategy you can simply replace the word “study” with “strategy” in the second line.

Then let’s add a new line and use the function “strategy.entry()”.

After finishing with the colors, we have managed to add two new changes, converting the study into a strategy and adding the function “strategy.entry()” so TradingView can take our entry signal and simulate how it would have performed in the past. Below is our new code.

//@version=4
strategy(“My Script”, overlay = true)
swmaClose = swma(close)
vwapClose = vwap(close)
swmaLong = close > swmaClose
vwapLong = close > vwapClose
triggerLong = vwapLong and not vwapLong[1] and not swmaLong and not swmaLong[1]
saveLong = false, saveLong := triggerLong ? true : not vwapLong ? false : saveLong[1]
startLong = saveLong and swmaLongstrategy.entry(“Open Long”, strategy.long, when = startLong)// bgcolor(swmaLong ? color.blue : na)
// bgcolor(vwapLong ? color.orange : na)
// bgcolor(triggerLong ? color.purple : na)
// bgcolor(saveLong ? color.yellow : na)
bgcolor(startLong ? color.green : na)

Once we add it to charts, we see nothing really happens.

If we take a look at “List of Trades” we will see that there is only one trade and it is still open.

As you probably guessed, there is no exit condition yet, so that is why. While there are many ways we can create one, this time I have chosen to go with simple take profit/stop loss.

Notice TradingView offers risk exits into two flavors, the exact price you tell it and the amount of ticks away from the entry price logged into the backtest engine. To code risk exits we need the function “strategy.exit()”.

Because the focus of this article is to express the simple way, I chose to go with ticks. We should place the “strategy.exit()” function below the “strategy.entry()” one, while there is no rule to which strategy function should come first, since it will behave the same way, personally I chose below.

strategy.exit(“Exit Long”, “Open Long”, profit = stopLoss, loss = takeProfit)

Once we try to add it to charts, we get the following error.

Whoops, we forgot to add those variables, so let’s do it below.

stopLoss = input(250, “Stop Loss”, step = 50)
takeProfit = input(10, “Reward/Risk”) * stopLoss

There you see a new function “input()”.

Now you know what “input()” does, notice on the line we mention “takeProfit”; how we ask for another input and multiply by the value of “stopLoss”. We do that so we are aware of our reward to risk ratio, which is the most effective way to succeed in trading. Now our script should work. In case you are a bit lost, our code should look like below.

//@version=4
strategy("My Script", overlay = true)
swmaClose = swma(close)
vwapClose = vwap(close)
swmaLong = close > swmaClose
vwapLong = close > vwapClose
triggerLong = vwapLong and not vwapLong[1] and not swmaLong and not swmaLong[1]
saveLong = false, saveLong := triggerLong ? true : not vwapLong ? false : saveLong[1]
startLong = saveLong and swmaLongstopLoss = input(250, "Stop Loss", step = 50)
takeProfit = input(10, "Reward/Risk") * stopLoss
strategy.entry("Open Long", strategy.long, when = startLong)
strategy.exit("Exit Long", "Open Long", profit = stopLoss, loss = takeProfit)
// bgcolor(swmaLong ? color.blue : na)
// bgcolor(vwapLong ? color.orange : na)
// bgcolor(triggerLong ? color.purple : na)
// bgcolor(saveLong ? color.yellow : na)
bgcolor(startLong ? color.green : na)

Finally we have something.

There are quite a bit of things we still need to take care of. Let’s start with changing the parameters that go into the backtest engine so it can behave more like live trading.

The key to it is found inside the second line “strategy()”.

Once you have read and grasp the basics of it, I want to give you the parameters I personally use every time. You can replace the values inside “title” and “shorttitle” with your new ones each time you build a new strategy.

//@version=4
strategy(title = "Simple Price Momentum", shorttitle = "SPM", overlay = true, initial_capital = 20000, default_qty_value = 100, default_qty_type = strategy.percent_of_equity, commission_value = 0.025)

If you compare the metrics between the one before and now you can observe now it reflects more realistic results.

One thing we can notice is the positions being opened one candle or bar after the Green color.

Basically, while the Green shows earlier, the signal actually takes place at the end of the bar. Remember “close” source is the current price while the candle is still forming, because it can change its state it could create a situation where “A > B” and then change to “A < B”, so we don’t know which one is the final value until the bar officially ends. This is why the backtest engine opens a position in the bar after.

To make Green matches with backtesting, we can add “[1]” to “startLong” inside the “bgcolor()”.

bgcolor(startLong[1] ? color.green : na)

Then it will look like below.

For more about how strategies works, read this.

We still have one remaining matter, have you notice something strange taking place?

Green continues to be visible bar after bar, the signal is strongest when it first occurs, as you know trends don’t remain the same forever, so it is a matter of time before things switch up. As you can see above, it opened two positions under the same signal.

Below is an example of how that can be bad.

The way I address this is by introducing a position or order management logic. The simplest way to represent that is by creating a condition that says “allow this when it is true now and not in the bar before”. We can code that as follows.

startLong = saveLong and swmaLong
startLong := startLong and not startLong[1]

To understand what took place, see below.

Now before testing our new code, I want to take the previous order management rule and turn into something optional using an “input()”.

startLong := input(false, "Consecutive Orders") ? startLong : startLong and not startLong[1]

Now we have more flexibility in the way our trading system operates.

Check below how when “Consecutive Orders” is off, which means our order management logic is in place, see how our results improve, this is due to the fact we managed to remove even more noise through it.

Well, that is it. We have successfully created a strategy using a logical approach, added risk parameters, and improved the backtest results.

Our final script should be similar to below.

//@version=4
strategy(title = "Simple Price Momentum", shorttitle = "SPM", overlay = true, initial_capital = 20000, default_qty_value = 100, default_qty_type = strategy.percent_of_equity, commission_value = 0.025)
swmaClose = swma(close)
vwapClose = vwap(close)
swmaLong = close > swmaClose
vwapLong = close > vwapClose
triggerLong = vwapLong and not vwapLong[1] and not swmaLong and not swmaLong[1]
saveLong = false, saveLong := triggerLong ? true : not vwapLong ? false : saveLong[1]
startLong = saveLong and swmaLong
startLong := input(false, "Consecutive Orders") ? startLong : startLong and not startLong[1]
stopLoss = input(250, "Stop Loss", step = 50)
takeProfit = input(10, "Reward/Risk") * stopLoss
strategy.entry("Open Long", strategy.long, when = startLong)
strategy.exit("Exit Long", "Open Long", profit = stopLoss, loss = takeProfit)
// bgcolor(swmaLong ? color.blue : na)
// bgcolor(vwapLong ? color.orange : na)
// bgcolor(triggerLong ? color.purple : na)
// bgcolor(saveLong ? color.yellow : na)
bgcolor(startLong[1] ? color.green : na)

While not perfect, and definitely in need of more advanced logic. You should have gotten my approach and I hope you have learned something just like I did by sharing all this. You can repeat these steps, add more sophisticated code, and make it your own.

One thing I recommend is reading these Pine Coding Conventions. I must admit those weren’t available when I started years ago, so while I’m aware I’m recommending them, I must take the advice myself; like the saying goes, “You can’t teach an old dog new tricks.” Anyway, the earlier you start learning right, the better. You can visit PineCoders to learn more and become a 10x Pine Script developer.

Thank you for reading, I hope you not only succeed on your trading career but also enjoy it.

--

--

QS

Hard core trader/programmer, not fully either of both, yet a new breed of thinker that solves outside problems to find his own purpose on this world.