Financial engineering broadly encompasses corporate finance, securities investment, forex trading, and derivatives trading, requiring financial engineers to possess the theory, knowledge, and skills that match their responsibilities in financial engineering.
Emanuel Derman’s 2007 book “My Life as a Quant” was rated one of BusinessWeek’s top ten books. He writes about quants: “Quantitative financiers, rigorously trained in science, are the creators of these models and the future stars on Wall Street.” Emanuel Derman is the Director of the Financial Engineering program at Columbia University, a columnist for “Risk” magazine, and an investment risk management consultant.
Derman, a graduate of Columbia University with a Ph.D. in theoretical physics, joined the derivative products revolution on Wall Street after working at AT&T Bell Laboratories. Since 1985, he has worked for renowned investment banks such as Goldman Sachs and Salomon Brothers.
Derman has contributed to financial product innovation, co-creating widely used models like the Black-Derman-Toy interest rate model and the Derman-Kani local volatility model. He was elected Financial Engineer of the Year by the International Association of Financial Engineers in 2000 and was inducted into the “Risk” magazine Hall of Fame in 2002. Quants have played a significant role in enhancing the quantification of forex trading, although they are not entirely systematic traders.
Let’s look at the types of quants. Quants are categorized into six types:
- Desk Quants develop pricing models directly used by traders.
- Model Validating Quants independently develop pricing models and verify the accuracy of models developed by Desk Quants.
- Research Quants try to invent new pricing formulas and models.
- Quant Developers write code or debug large systems developed by others.
- Statistical Arbitrage Quants search for patterns in data for automated trading systems (arbitrage systems), a technique quite different from derivatives pricing, mainly used in hedge funds.
- Capital Quants establish credit and capital models for banks (Basel I Accord).
Quants closely associated with forex trading design, test, and optimize trading programs, including discovering arbitrage opportunities. We’ve talked a lot about famous figures and concepts in quantitative systematic trading; now, let’s look at something more intuitive and concrete.
Automated trading plays an important role in the globally used Metatrader 4.0 software (see below), with many programs available in the intelligent trading system list. One such program is “MACD Sample,” which we will analyze as the simplest automated trading program.
The following is the code for the MACD Sample automated trading program, with annotations explaining the meaning of each line of code. Understanding this automated trading program’s code will give a clearer insight into MT4’s automated trading programs.
//+------------------------------------------------------------------+
//| MACD Sample.mq4 |
//+------------------------------------------------------------------+
input double TakeProfit =50; # The number of points to aim for as a profit target
input double Lots =0.1; # The number of lots to enter per trade
input double TrailingStop =30; # The number of points to set for the trailing stop loss
input double MACDOpenLevel =3; # The reference position for opening a trade based on the MACD indicator
input double MACDCloseLevel=2; # The reference position for exiting a trade based on the MACD indicator
input int MATrendPeriod =26; # The number of periods used for the Moving Average (MA) in the conditions
The data at the top of the program starting with input
are program parameters, meaning they can be modified by the user when the EA is called. This EA is a complete demonstration of common technical indicator conditions for entry and exit, along with a moving stop loss feature, making it suitable for forex traders to study.
Let’s summarize the basic conditions of the program for easy understanding:
Long Entry Conditions:
- MACD is less than 0 and below the specified parameter
MACDOpenLevel
, and the MACD signal line crosses below the baseline (bearish crossover) with an upward trending MA.
Long Exit Conditions:
- MACD is greater than 0 and above the specified parameter
MACDCloseLevel
, and the MACD signal line crosses above the baseline (bullish crossover).
Short Entry Conditions:
- MACD is greater than 0 and above the specified parameter
MACDOpenLevel
, and the MACD signal line crosses above the baseline (bullish crossover) with a downward trending MA.
Short Exit Conditions:
- MACD is less than 0 and below the specified parameter
MACDCloseLevel
, and the MACD signal line crosses below the baseline (bearish crossover).
With this preliminary understanding, let’s begin analyzing the basic structure of the EA program:
The start()
function is the most crucial part of execution. It automatically runs once for every price update, so the main logic structure is within this function.
The basic process of the program follows these steps. First, we remember this structure, then we match it to understand the program. Initially, we determine the current position status because the start
function runs in a loop. Therefore, each step in the middle will use the start
function. At the beginning of the function, we first obtain the current position status through MT4’s position operation function and further calculate different branches according to the status.
The following two parts at the beginning of the program are not important, but to mention briefly:
if (Bars < 100)
{
Print("bars less than 100");
return(0);
}
This means if the current chart has fewer than 100 bars, then do not perform calculations, just return. This situation generally does not occur, so we can omit this part when writing our programs.
if (TakeProfit < 10)
{
Print("TakeProfit less than 10");
return(0); // check TakeProfit
}
The above means if the parameter TakeProfit
for the trailing stop points is set less than 10 points, then issue a warning and do not perform calculations. This is to prevent setting random values that cause errors in later calculations. If the program is for personal use, such a low-level error is unlikely, so we can also omit this when writing the program.
The following section:
MacdCurrent = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_MAIN, 0);
MacdPrevious = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_MAIN, 1);
SignalCurrent = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_SIGNAL, 0);
SignalPrevious = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_SIGNAL, 1);
MaCurrent = iMA(NULL, 0, MATrendPeriod, 0, MODE_EMA, PRICE_CLOSE, 0);
MaPrevious = iMA(NULL, 0, MATrendPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);
This part is variable assignment, equivalent to pre-calculating the current MACD and MA values used later. Writing this out in advance makes it clear when using the assigned variables later, which is a good programming habit.
Now we enter the main logic part of the program, the first thing we encounter is the part mentioned above about obtaining the current status through the position function.
total = OrdersTotal() # Get the number of current open orders
If the number of open orders is less than 1, it indicates an empty position state, then we judge the entry conditions for long and short positions. If the conditions are met, we proceed with the entry. The code is as follows:
{
// no opened orders identified
if (AccountFreeMargin() < (1000 * Lots))
This checks whether there is enough free margin to place an order. If not, it returns directly without proceeding to the subsequent entry judgment.
{
Print("Free Margin =", AccountFreeMargin())
return(0);
}
//check for long position (BUY) possibility
if (MacdCurrent < 0 && MacdCurrent > SignalCurrent && MacdPrevious < SignalPrevious &&
MathAbs(MacdCurrent) > (MACDOpenLevel * Point) && MaCurrent > MaPrevious)
This is the condition for a long position entry. There are two techniques to note: one is the mathematical meaning of a crossover is “previously below, now above” or vice versa; the second is the mathematical meaning of an upward MA trend if the current bar’s MA is greater than the previous bar’s MA value.
if (ticket > 0) # greater than 0 indicates successful entry
{
if (OrderSelect (ticket, SELECT_BY_TICKET, MODE_TRADES))
Print("BUY order opened:", OrderOpenPrice());
}
else Print("Error opening BUY order:", GetLastError());
This is for unsuccessful entry, outputting the system reason for failure.
return(0)
Use return because one scenario is successful entry, then directly return to wait for the next price to execute the start function again; another scenario is unsuccessful entry, then return is also waiting for the next price to come to execute the entry operation again.
The judgment for short position entry is similar, and you can compare it yourself.
if (MacdCurrent > 0 && MacdCurrent < SignalCurrent && MacdPrevious > SignalPrevious &&
MacdCurrent > (MACDOpenLevel * Point) && MaCurrent < MaPrevious)
{
ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, 3, 0, Bid - TakeProfit * Point, "macd sample", 16384, 0, Red);
if (ticket > 0)
{
if (OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES))
Print("SELL order opened:", OrderOpenPrice());
}
else Print("Error opening SELL order:", GetLastError());
return(0);
}
return(0);
}
The above is a step-by-step explanation of the automatic trading code. The complete automatic trading code is shown below.
//+------------------------------------------------------------------+
//| MACD Sample.mq4 |
//| Copyright 2005-2014, MetaQuotes Software Corp. |
//| http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright "2005-2014, MetaQuotes Software Corp."
#property link "http://www.mql4.com"
input double TakeProfit =50;
input double Lots =0.1;
input double TrailingStop =30;
input double MACDOpenLevel =3;
input double MACDCloseLevel=2;
input int MATrendPeriod =26;
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
void OnTick(void)
{
double MacdCurrent,MacdPrevious;
double SignalCurrent,SignalPrevious;
double MaCurrent,MaPrevious;
int cnt,ticket,total;
//---
// initial data checks
// it is important to make sure that the expert works with a normal
// chart and the user did not make any mistakes setting external
// variables (Lots, StopLoss, TakeProfit,
// TrailingStop) in our case, we check TakeProfit
// on a chart of less than 100 bars
//---
if(Bars<100)
{
Print("bars less than 100");
return;
}
if(TakeProfit<10)
{
Print("TakeProfit less than 10");
return;
}
//--- to simplify the coding and speed up access data are put into internal variables
MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0);
MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0);
SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,1);
MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0);
MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);
total=OrdersTotal();
if(total<1)
{
//--- no opened orders identified
if(AccountFreeMargin()<(1000*Lots))
{
Print("Free Margin = ",AccountFreeMargin());
return;
}
//--- check for long position (BUY) possibility
if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious &&
MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && MaCurrent>MaPrevious)
{
ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point,"macd sample",16384,0,Green);
if(ticket>0)
{
if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
Print("BUY order opened : ",OrderOpenPrice());
}
else
Print("Error opening BUY order : ",GetLastError());
return;
}
//--- check for short position (SELL) possibility
if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious &&
MacdCurrent>(MACDOpenLevel*Point) && MaCurrent<MaPrevious)
{
ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,Bid-TakeProfit*Point,"macd sample",16384,0,Red);
if(ticket>0)
{
if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
Print("SELL order opened : ",OrderOpenPrice());
}
else
Print("Error opening SELL order : ",GetLastError());
}
//--- exit from the "no opened orders" block
return;
}
//--- it is important to enter the market correctly, but it is more important to exit it correctly...
for(cnt=0;cnt<total;cnt++)
{
if(!OrderSelect(cnt,SELECT_BY_POS,MODE_TRADES))
continue;
if(OrderType()<=OP_SELL && // check for opened position
OrderSymbol()==Symbol()) // check for symbol
{
//--- long position is opened
if(OrderType()==OP_BUY)
{
//--- should it be closed?
if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious &&
MacdCurrent>(MACDCloseLevel*Point))
{
//--- close order and exit
if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet))
Print("OrderClose error ",GetLastError());
return;
}
//--- check for trailing stop
if(TrailingStop>0)
{
if(Bid-OrderOpenPrice()>Point*TrailingStop)
{
if(OrderStopLoss()<Bid-Point*TrailingStop)
{
//--- modify order and exit
if(!OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,OrderTakeProfit(),0,Green))
Print("OrderModify error ",GetLastError());
return;
}
}
}
}
else // go to short position
{
//--- should it be closed?
if(MacdCurrent<0 && MacdCurrent>SignalCurrent &&
MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*Point))
{
//--- close order and exit
if(!OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet))
Print("OrderClose error ",GetLastError());
return;
}
//--- check for trailing stop
if(TrailingStop>0)
{
if((OrderOpenPrice()-Ask)>(Point*TrailingStop))
{
if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0))
{
//--- modify order and exit
if(!OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop,OrderTakeProfit(),0,Red))
Print("OrderModify error ",GetLastError());
return;
}
}
}
}
}
}
//---
}
//+------------------------------------------------------------------+