mpf8_Building an Algorithmic Trading Platform_OANDA v20_pyyaml_mean-reverting_trend-following_VaRisk

     Algorithmic trading automates the systematic trading process, where orders are executed at the best price possible based on a variety of factors, such as pricing, timing, and volume. Brokerage firms may offer an Application Programming Interface (API) as part of their service offering for customers who wish to deploy their own trading algorithms. An algorithmic trading system must be highly robust to handle any point of failure during the order execution. Network configuration, hardware, memory management, speed, and user experience are a number of factors to be considered when designing a system for executing orders. Designing larger systems inevitably adds more complexity to the framework.

     As soon as a position in a market is opened, it is subjected to various types of risk, such as market risk, interest rate risk, and liquidity risk. To preserve the trading capital as much as possible, it is important to incorporate risk management measures into the trading system. Perhaps the most common risk measure used in the financial industry is the Value at Risk (VaR) technique. We will discuss the beauty and flaws of VaR, and how it can be incorporated into our trading system that we will develop in this chapter.

In this chapter, we will cover the following topics:

  • An overview of algorithmic trading
  • A list of brokers and system vendors with a public API
  • Choosing a programming language for a trading system
  • Designing an algorithmic trading platform
  • Setting up API access on the Oanda v20 Python module
  • Implementing a mean-reverting algorithmic trading strategy
  • Implementing a trend-following algorithmic trading strategy
  • Introducing VaR for risk management in our trading system
  • Performing VaR calculations in Python on AAPL

Introducing algorithmic trading

     In the 1990s, exchanges had already begun to use electronic trading systems. By 1997, 44 exchanges worldwide used automated systems for trading futures and options with more exchanges in the process of developing automated technology. Exchanges such as the Chicago Board of Trade (CBOT) and the London International Financial Futures and Options Exchange (LIFFE) used their electronic trading systems as an after-hours complement to the traditional open outcry/ˈaʊtkraɪ/公开喊价,大声疾呼,尖叫 trading pits, thus giving traders 24-hour access to the exchange's risk management tools. With these improvements in technology, technology-based trading became less expensive, fueling/ ˈfjuːəlɪŋ /给……提供燃料,推动 the growth of trading platforms that are faster and more powerful. The higher reliability of order execution and the lower rate of message transmission error has deepened the reliance on technology by financial institutions. The majority of asset managers, proprietary/ prəˈpraɪəteri / traders自营交易员, and market makers have since moved from the trading pits to electronic trading floors.

     As systematic or computerized trading becomes more commonplace常见的东西, speed is the most important factor in determining the outcome of a trade. Quants定量分析专家,金融工程师, by utilizing sophisticated fundamental models, are able to recompute fair values of trading products on the fly and execute trading decisions, enabling them to reap/riːp/获得,收获 profits at the expense of those fundamental traders using traditional tools. This has given way to the term high-frequency trading (HFT), which relies on fast computers to execute the trading decisions before anyone else can. In fact, HFT has evolved into a billion-dollar industry.

     Algorithmic trading refers to the automation of the systematic trading process, where the order execution is heavily optimized to give the best price possible. It is not part of the portfolio allocation process.

     Banks, hedge funds, brokerage firms, clearing firms, and trading firms typically have their servers placed right next to the electronic exchange to receive the latest market prices and to perform the fastest order execution where possible. They bring enormous/ɪˈnɔːrməs/巨大的 trading volumes to the exchange. Anyone who wishes to participate in low-latency, high-volume trading activities (such as complex event processing or capturing fleeting/ˈfliːtɪŋ/短暂的 price discrepancies/dɪˈskrepənsi/差异) by acquiring exchange connectivity may do so in the form of co-location主机代管, where their server hardware can be placed on a rack right next to the exchange for a fee.

     The Financial Information eXchange (FIX, https://blog.csdn.net/Linli522362242/article/details/122613472) protocol is the industry standard for electronic communications with the exchange from the private server for direct market access (DMA) to real-time information. C++ is the common choice of programming language for trading over the FIX protocol, though other languages, such as .NET Framework Common Language and Java can also be used. The Representational State Transfer(REST) API offerings are becoming more common for retail investors. Before creating an algorithmic trading platform, you will need to assess various factors, such as the speed and ease of learning before deciding on a specific language for the purpose.

     Brokerage firms will provide a trading platform of some sort for their customers in order for them to execute orders on selected exchanges in return for the commission fees. Some brokerage firms may offer an API as part of their service offering to technically-inclined customers who wish to run their own trading algorithms. In most circumstances, customers may also choose from a number of commercial trading platforms offered by third-party vendors. Some of these trading platforms may also offer API access to route orders electronically to the exchange. It is important to read the API documentation beforehand to understand the technical capabilities offered by your broker and to formulate an approach in developing an algorithmic trading system.

Trading platforms with a public API

     The following table lists some brokers and trading platform vendors who have their API documentation publicly available:

Broker/vendorURLProgramming languages supported
CQGhttps://www.cqg.comREST, FIX, C#, C++, and VB/VBA
Cunningham
Trading Systems
http://www.ctsfutures.comMicrosoft .Net Framework 4.0 and FIX
E*Tradehttps://developer.etrade.com/homePython, Java, and Node.js
Interactive Brokershttps://www.interactivebrokers.com/en/index.php?f=5041Java, C++, Python, C#, C++, and DDE
IGhttps://labs.ig.com/REST, Java, JavaScript, .NET, Clojure, and Node.js
Tradierhttps://developer.tradier.com/REST
Trading
Technologies
https://www.tradingtechnologies.com/trading/apis/REST, .NET, and FIX
OANDAhttps://developer.oanda.com/REST, Java, and FIX
FXCMhttps://www.fxcm.com/uk/algorithmic-trading/api-trading/REST, Java, and FIX

11_14_TheFXCMTradingPlatform,
t8_Connecting to Trading Exchanges_WMFrame 5.1_PowerShell_quickFIX_colab

https://blog.csdn.net/Linli522362242/article/details/102816913,https://blog.csdn.net/Linli522362242/article/details/122613472

FXCM, 

quickFIX

Choosing a programming language

     With many options of programming languages available to interface with brokers or vendors, the question that comes naturally to anyone starting out in algorithmic trading platform development is Which language should I use?

     Before answering this question, it is important to find out if your broker provides developer tools. RESTful APIs are becoming the most common offering alongside FIX protocol access. A small number of brokers support Java and C#. With RESTful APIs, it is easy to search for, or even write a wrapper around it in almost any programming language that supports the HyperText Transfer Protocol (HTTP).

     Bear in mind that each tool option presents its own limitations. Your broker may rate-limit price and event updates. How your product will be developed, the performance metrics to follow, the costs involved, latency threshold, risk measures, and the expected user interface are pieces of the puzzle to be taken into consideration. The risk manager, execution engine, and portfolio optimizer are some major components that will affect the design of your system. Your existing trading infrastructure, choice of operating system, programming language compiler capability, and available software tools pose further constraints on the system design, development, and deployment.

System functionalities

     It is important to define the outcomes of your trading system. An outcome could be a research-based system concerned with obtaining high-quality data from data vendors, performing computations or running models, and evaluating a strategy through signal generation. Part of the research component might include a data-cleaning module or a backtesting interface to run a strategy with theoretical parameters over historical data. The CPU speed, memory size, and bandwidth are factors to be considered while designing our system.

     Another outcome could be an execution-based system that is more concerned with risk management and order handling features to ensure the timely execution of multiple orders. The system must be highly robust in order to handle any point of failure during the order execution. As such, network configuration, hardware, memory management and speed, and user experience are some factors to be considered when designing a system that executes orders.

     A system may contain one or more of these functionalities. Designing larger systems inevitably adds complexity to the framework. It is recommended that you choose one or more programming languages that can address and balance the development speed, ease of development, scalability, and reliability of your trading system.

Building an algorithmic trading platform

     In this section, we will design and build a live algorithmic trading system in Python. Since developer tools and offerings vary with each broker, it is important to take into consideration the different programming implementation that is required in integrating with our very own trading system. With a good system design, we can build a generic service that allows configurations of different brokers to plug in and play together nicely with our trading system.

Designing a broker interface

     When designing a trading platform, the following three functionalities are highly desirable to fulfill any given trading plan:

  • Getting prices: Pricing data is the one of the most basic units of information available from an exchange. It represents the quotation prices that are made by the marketplace to buy or sell a traded product. A broker may redistribute data from the exchange to be delivered to you in its own format. The most basic form of price data available is the date and time of the quotes, the symbol of the traded product, and the quoted bidding and asking price of the traded product. More often than not, this pricing data is useful to base your trading decisions on.
         The following diagram describes the communication between the initiator发起者, which is the trading system, and the acceptor, which is the exchange:

         The following diagram represents the FIX messages that are exchanged between the acceptor and the initiator
    • The best quoted bidding and asking prices are known as the Level 1 quote. In most cases, it is possible to request for Level 2, 3, or even additional quote levels from your broker.
  • Sending orders to the market: When sending orders to the marketplace, it may or may not be executed by your broker or the exchange. If it does get executed, you will have opened a position in the traded product and subjected yourself to all forms of risk, as well as returns. The simplest form of order states the product to trade (usually denoted by a symbol), the quantity to trade, the position that you want to take (that is, whether you are buying or selling), and, for a non-market order, theprice to trade at. Depending on your needs, there are many different types of orders available to help manage your trading risks.

         The trading system will communicate the orders to the exchange by opening a trading session with the exchange. While this active trading session stays open, order messages will be sent to the exchange. The exchange will communicate the state of these orders by using FIX messages. This is shown in the following diagram:

         The following diagram represents the FIX messages that are exchanged between the initiator and the acceptor:
    • Your broker may not support all order types. It is prudent to check with your broker which types of order are available and which can best manage your trading risks. The most common order type used by market participants are market orders, limit orders, and good-till-canceled orders.

      A market order is an order to buy or sell a product right away in the market. Because it is executed based on the current market prices, an execution price is not required for this type of order. Typically, if you are going to buy a stock, then you will pay a price at or near the posted ask. If you are going to sell a stock, you will receive a price at or near the posted bid.
           One important thing to remember is that the last traded price is not necessarily the price at which the market order will be executed. In fast-moving and volatile markets, the price at which youactually execute (or fill) the trade can deviate fromthe last traded price. The price will remain the same only when the bid/ask price is exactly at the last traded price.
           Market orders are popular among individual investors who want to buy or sell a stock without delay. The advantage of using market orders is that you are guaranteed to get the trade filled;in fact, it will be executed as soon as possible. Although the investor doesn't know the exact price at which the stock will be bought or sold, market orders on stocks that trade over tens of thousands of shares per day will likely be executed close to the bid/ask prices

      A limit order is an order to buy or sell a product at a specific or better price.
           A limit order, sometimes referred to as a pending order, allows investors to buy and sell securities at a certain price in the future. This type of order is used to execute a trade if the price reaches the pre-defined level; the order will not be filled if the price does not reach this level. In effect, a limit order sets themaximum or minimum price at which you are willing to buy or sell.
           For example, if you wanted to buy a stock at $10, you could enter a limit order for this amount. This means that you would not pay one cent over $10 for that particular stock. However, it is still possible that you could buy it for less than the $10 per share specified in the order.
           There are 4 types of limit orders:
           Buy Limit: an order to purchase a security at or below a specified price. Limit orders must be placed on the correct side of the market to ensure they will accomplish the task of improving the price. For a buy limit order, this means placing the order at or below the current market bid.(The reason is that you think the stock price will fall in the recent period, but will rise in the future)
           Sell Limit: an order to sell a security at or above a specified price. To ensure an improved price, the order must be placed at or above the current market ask.(The reason is that you think the stock price will rise in the recent period or the price will reach your expected price, but it will fall in the future, so sell it for profit)
           Buy Stop: an order to buy a security at a price above the current market bidThe reason is that you predict that the stock price will continue to rise, and set a limit for the rise of the stock price. If your limit price has been reached in the recent period of time, it proves that your prediction is correct. After buying, the price is likely to continue rise.). A stop order to buy becomes active only after a specified price level has been reached (known as the stop level). Buy stop are orders placed above the market and sell stop orders placed below the market(The reason is that you have set a limit for the price of the stock to fall. If the stock falls to your limit price (the maximum loss you can afford) in the recent period of time, it proves that your prediction is correct, and you can sell it and stop the loss in time.)
           (the opposite of buy and sell limit orders, respectively). Once a stop level has been reached, the order will be immediately converted into a market or limit order.
           Sell Stop: an order to sell a security at a price below the current market ask. Like the buy stop, a stop order to sell becomes active only after a specified price level has been reached.

           When deciding between amarket or limit order, investors should be aware of the added costs. Typically, the commissions are cheaper for market orders than for limit orders. The difference in commission can be anywhere from a couple of dollars to more than $10. For example, a $10 commission on a market order can be boosted up to $15 when you place a limit restriction on it. When you place a limit order, make sure it's worthwhile.

           Let's say your broker charges $7 for a market order and $12 for a limit order. Stock XYZ is presently trading at $50 per share and you want to buy it at $49.90. By placing a market order to buy 10 shares, you pay $500 (10 shares x $50 per share) + $7 commission, which is a total of $507. By placing a limit order for 10 shares at $49.90, you would pay $499 + $12 commissions, which is a total of $511.

           Even though you save a little from buying the stock at a lower price (10 shares x $0.10 = $1), you will lose it in the added costs for the order ($5), a difference of $4. Furthermore, in the case of the limit order, it is possible that the stock doesn't fall to $49.90 or less. Thus, if it continues to rise, you may lose the opportunity to buy.

      A good-till-canceled (GTC) order is an order that remains in the exchange queue for execution until the stated expiry time. Unless specified, most orders are GTC orders that expire at the end of the trading day. This is a time restriction that you can place on different orders. A good-til-canceled order will remain active until you decide to cancel it. Brokerages will typically limit the maximum time you can keep an order open (or active) to 90 days
      You can find more information of various order types at https://www.investopedia.com/investing/basics-trading-stock-know-your-orders/
  • Tracking positions: As soon as your order is executed, you will enter into a position. Keeping track of your opened position will help determine how well (or badly!) your trading strategy is doing, as well as manage and plan your risks. Your gains and losses from opened positions vary according to market movements, and is known as unrealized profits and losses. After closing your position, you will have realized profits and losses, which are the final outcome of your trading strategy.
    P&LRealized (points) = (Sell Price - Buy Price) * Qty
    P&LUnrealized (points) = (Theoretical Exit Price - Average Open Price) * Position

    P&LTotal (points)= P&LRealized (points) + P&LUnrealized (points)
    https://blog.csdn.net/Linli522362242/article/details/121896073

     With these three basic functionalities in mind, we can design a generic Broker class implementing these functions that can be easily extended to any broker-specific configurations. 

Python library requirements 

     In this chapter, we will be using the publicly-available v20 module with Oanda as our broker. All method implementations mentioned in this chapter uses the v20 Python library as an example.

Installing v20

     The official repository for OANDA v20 REST API is at https://github.com/oanda/v20-python. Install using pip with the terminal command:

pip install v20

     Detailed documentation on the use of the OANDA v20 REST API can be found at https://developer.oanda.com/rest-live-v20/introduction/. The use of APIs varies with each broker, so make sure that you consult with your broker for the appropriate documentation before writing your trading system implementation. 

Writing an event-driven broker class

     Whether we are fetching prices, sending orders, or tracking positions, an event-driven system design willtrigger key parts of our system in a multi-threaded fashion without blocking the main thread.

Let's begin writing our Broker class in Python, as follows:

from abc import abstractmethod

class Broker( object ):
    def __init__(self, host, port):
        self.host = host
        self.port = port
        
        self.__price_event_handler = None
        self.__order_event_handler = None
        self.__position_event_handler = None

     In the constructor, we can provide the host and port public connection configurations of our broker for the inheriting child classes. 3 variables are declared for storing the event handlers of prices, orders, and position updates, respectively. Here, we are designing for only one listener for each event. A more complex trading system might support multiple listeners on the same event handler.

Storing the price event handler

     Inside the Broker class, add the following 2 methods as the getter and setter for the price event handler, respectively: 

    # @property:https://www.programiz.com/python-programming/property    
    @property
    def on_price_event( self ):
        """
        Listeners will receive: symbol(ticker), bid, ask
        """
        return self.__price_event_handler
    
    @on_price_event.setter
    def on_price_event( self, event_handler ):
        self.__price_event_handler = event_handler

     After an order is routed to your broker, the inheriting child classes will notify the listeners through the on_order_event method invocation, along with the order transaction ID.

Storing the position event handler

     Add the following two methods as the getter and setter for the position event handler:

    @property
    def on_position_event(self):
        """
        Listeners will receive: symbol, is_long, units, unrealized_pnl, pnl
        """
        return self.__position_event_handler
    
    @on_position_event.setter
    def on_position_event( self, event_handler):
        self.__position_event_handler = event_handler

     When a position update event is received from your broker, the inheriting child classes will notify the listeners through the on_position_event method invocation containing the symbol information, a flag indicating a long or short position,the number of units traded, the unrealized profit and loss, and the realized profit and loss.

Declaring an abstract method for getting prices

     Since fetching prices from a data source is a main requirement of any trading system, create an abstract method named get_prices() to perform such a function. It expects a symbols parameter to contain a list of broker-defined symbols, that will be used for querying data from our broker. The inheriting child classes are expected to implement this method, otherwise a NotImplementedError exception is thrown:

    @abstractmethod
    def get_price( self, symbols=[] ):
        """
        Query market prices from a broker
        : param symbols: list of symbols recognized by your broker
        """
        raise NotImplementedError('Method is required!')

     Note that this get_prices() method is expected to perform a one-time fetch of the current market prices. This gives us a snapshot of the market at a particular time. For a continuously-running trading system, we will require streaming market prices to feed our trading logic in real time, which we will define next.

Declaring an abstract method for streaming prices

     Add a stream_prices() abstract method that accepts a list of symbols in streaming prices using the following code:

    @abstractmethod
    def stream_prices( self, symbols=[] ):
        """
        Continuously stream prices from a broker.
        : param symbols: list of symbols recognized by your broker
        """
        raise NotImplementedError("Method is required!")

     The inheriting child classes are expected to implement this method in streaming prices from your broker, otherwise a NotImplementedError exception message will be thrown.

Declaring an abstract method for sending orders

     Add a send_market_order() abstract method for the inheriting child classes to implement when sending a market order to your broker:

    @abstractmethod
    def send_market_order( self, symbol, quantity, is_buy ):
        raise NotImplementedError("Method is required!")

     Using the preceding methods written in our Broker base class, we can now write broker-specific classes in the next section.

from abc import abstractmethod

class Broker( object ):
    def __init__(self, host, port):
        self.host = host
        self.port = port
        
        self.__price_event_handler = None
        self.__order_event_handler = None
        self.__position_event_handler = None
        
    # @property:https://www.programiz.com/python-programming/property    
    @property
    def on_price_event( self ):
        """
        Listeners will receive: symbol(ticker), bid, ask
        """
        return self.__price_event_handler
    
    @on_price_event.setter
    def on_price_event( self, event_handler ):
        self.__price_event_handler = event_handler
        
    @property
    def on_order_event( self ):
        """
        Listeners will receive: transaction_id
        """
        return self.__order_event_handler
    
    @on_order_event.setter
    def on_order_event( self, event_handler ):
        self.__order_event_handler = event_handler
        
    @property
    def on_position_event(self):
        """
        Listeners will receive: symbol, is_long, units, unrealized_pnl, pnl
        """
        return self.__position_event_handler
    
    @on_position_event.setter
    def on_position_event( self, event_handler):
        self.__position_event_handler = event_handler
        
    @abstractmethod
    def get_price( self, symbols=[] ):
        """
        Query market prices from a broker
        : param symbols: list of symbols recognized by your broker
        """
        raise NotImplementedError('Method is required!')
    
    @abstractmethod
    def stream_prices( self, symbols=[] ):
        """
        Continuously stream prices from a broker.
        : param symbols: list of symbols recognized by your broker
        """
        raise NotImplementedError("Method is required!")
        
    @abstractmethod
    def send_market_order( self, symbol, quantity, is_buy ):
        raise NotImplementedError("Method is required!")

Implementing the broker class

     In this section, we will implement the abstract methods of the Broker classthat are specific to our broker, Oanda. This requires the use of the v20 library. However, you can easily change the configuration and any implemented methods that are specific to a broker of your choice.

Initializing the broker class

pip install pyyaml

 

     Write the following OandaBroker class, which is specific to our broker, extending the generic Broker class:http://developer.oanda.com/rest-live-v20/development-guide/

import v20

class OandaBroker( Broker ):
    # http://developer.oanda.com/rest-live-v20/development-guide/
    
    # REST API
    # 120 requests per second. Excess requests will receive HTTP 429 error. 
    # This restriction is applied against the requesting IP address.
    PRACTICE_API_HOST = 'api-fxpractice.oanda.com'       # fxTrade Practice
    # Stream API
    # 20 active streams. Requests above this threshold will be rejected. 
    # This restriction is applied against the requesting IP address.
    PRACTICE_STREAM_HOST = 'stream-fxpractice.oanda.com' # fxTrade Practice
    
    # REST API
    LIVE_API_HOST = 'api-fxtrade.oanda.com'              # fxTrade
    # Stream API
    LIVE_STREAM_HOST = 'stream-fxtrade.oanda.com'        # fxTrade
    
    PORT = '443'
    
    def __init__( self, accountid, token, is_live = False ):
    
        if is_live:
            host = self.LIVE_API_HOST
            stream_host = self.LIVE_STREAM_HOST
        else:
            host = self.PRACTICE_API_HOST
            stream_host = self.PRACTICE_STREAM_HOST
        
        super( OandaBroker, self ).__init__(host, self.PORT)
        
        self.accountid = accountid
        self.token = token
        
        # https://github.com/oanda/v20-python/blob/master/src/v20/__init__.py
        self.api = v20.Context( host,       # hostname: The hostname of the v20 REST server 
                                self.port,  # port: The port of the v20 REST server
                                token=token 
                              )# token: The authorization token to use when making requests to the v20 server
        self.stream_api = v20.Context( stream_host, self.port, token=token )

     Note that Oanda uses two different hosts for regular API endpoints and a streaming API endpoint. These endpoints are different for their practice and live trading environments. All endpoints are connected on the standard Secure Socket Layer (SSL) port 443(Secure Sockets Layer (SSL) was designed to protect HTTP (Hypertext Transfer Protocol) data: HTTPS uses TCP port 443.). In the constructor, the is_live Boolean flag chooses the appropriate endpoints for the chosen trading environment for saving in the parent class. A True value for is_live indicates a live trading environment. The constructor argument also saves the account ID and token, which are required for authenticating the account used for trading. This information can be obtained from your broker.

     The api and stream_api variables hold the v20 library's Context objects that are used by calling methods to send instructions to your broker.

Implementing the method for getting prices

https://oanda-api-v20.readthedocs.io/en/master/endpoints/pricing/pricinginfo.html

{
  "prices": [
    {
      "status": "tradeable",
      "instrument": "EUR_USD",
      "quoteHomeConversionFactors": {
        "negativeUnits": "0.89160730",
        "positiveUnits": "0.89150397"
      },
      "asks": [
        {
          "price": "1.12170",
          "liquidity": 10000000
        },
        {
          "price": "1.12172",
          "liquidity": 10000000
        }
      ],
      "time": "2016-10-05T05:28:16.729643492Z",
      "closeoutAsk": "1.12174",
      "bids": [
        {
          "price": "1.12157",
          "liquidity": 10000000
        },
        {
          "price": "1.12155",
          "liquidity": 10000000
        }
      ],
      "closeoutBid": "1.12153",
      "unitsAvailable": {
        "default": {
          "short": "506246",
          "long": "506128"
        },
        "reduceOnly": {
          "short": "0",
          "long": "0"
        },
        "openOnly": {
          "short": "506246",
          "long": "506128"
        },
        "reduceFirst": {
          "short": "506246",
          "long": "506128"
        }
      }
    },
    {
      "status": "tradeable",
      "instrument": "EUR_JPY",
      "quoteHomeConversionFactors": {
        "negativeUnits": "0.00867085",
        "positiveUnits": "0.00866957"
      },
      "asks": [
        {
          "price": "115.346",
          "liquidity": 1000000
        },
        {
          "price": "115.347",
          "liquidity": 2000000
        },
        {
          "price": "115.348",
          "liquidity": 5000000
        },
        {
          "price": "115.350",
          "liquidity": 10000000
        }
      ],
      "time": "2016-10-05T05:28:15.621238671Z",
      "closeoutAsk": "115.350",
      "bids": [
        {
          "price": "115.329",
          "liquidity": 1000000
        },
        {
          "price": "115.328",
          "liquidity": 2000000
        },
        {
          "price": "115.327",
          "liquidity": 5000000
        },
        {
          "price": "115.325",
          "liquidity": 10000000
        }
      ],
      "closeoutBid": "115.325",
      "unitsAvailable": {
        "default": {
          "short": "506262",
          "long": "506112"
        },
        "reduceOnly": {
          "short": "0",
          "long": "0"
        },
        "openOnly": {
          "short": "506262",
          "long": "506112"
        },
        "reduceFirst": {
          "short": "506262",
          "long": "506112"
        }
      }
    }
  ]
}
    def process_price( self, price ):
        # The price object contains an instrument property of a string object, 
        symbol = price.instrument
        
        if not symbol:
            print( 'Price symbol is empty!' )
            return
         
        # along with list objects in the bids and asks properties. 
        # Typically, Level 1 quotes are available, 
        # so we read the first item of the each list.
        # Each item in the list is a price_bucket object, 
        # from which we extract the bid and ask price.
        
        bids = price.bids or []
        price_bucket_bid = bids[0] if bids and len(bids) > 0 else None
        bid = price_bucket_bid.price if price_bucket_bid else 0
        
        asks = price.asks or []
        price_bucket_ask = asks[0] if asks and len(asks) > 0 else None
        ask = price_bucket_ask.price if price_bucket_ask else 0
        
        self.on_price_event( symbol, bid, ask )

     The price object contains aninstrument property of a string object, along with list objects in the bids and asks properties. Typically, Level 1 quotes are available, so we read the first item of the each list. Each item in the list is a price_bucket object, from which we extract the bid and ask price.

     With this information extracted, we pass it to the on_price_event() event handler method. Note that, in this example, we are passing only three values. In more complex trading systems, you might want to consider extracting more detailed information, such as traded volume, last traded price, or multilevel quotes, and pass this to the price event listeners.

     The following codes implement the parent get_prices() method in the OandaBroker class for getting prices from your broker:

    def get_prices( self, symbols=[] ):    
    # accountID: Account Identifier
    # instruments: List of Instruments to stream Prices for.
    # snapshot: Flag that enables/disables the sending of a pricing snapshot
    #           when initially connecting to the stream.
    # includeUnitsAvailable: Flag that enables the inclusion of the
    #                        unitsAvailable field in the returned Price objects
    # https://oanda-api-v20.readthedocs.io/en/master/endpoints/pricing/pricinginfo.html
    # "unitsAvailable": {
    #                     "default": {
    #                                  "short": "506246",
    #                                  "long": "506128"
    #                                },
    #                     "reduceOnly": {
    #                                     "short": "0",
    #                                     "long": "0"
    #                                   },
    #                      "openOnly": {
    #                                    "short": "506246",
    #                                    "long": "506128"
    #                                  },
    #                      "reduceFirst": {
    #                                       "short": "506246",
    #                                       "long": "506128"
    #                                     }
    #                   }
    # https://github.com/oanda/v20-python/blob/master/src/v20/pricing.py
        response = self.api.pricing.get(
                                        self.accountid,
                                        # "instruments": "EUR_USD,EUR_JPY"
                                        instruments = ",".join( symbols ),
                                        snapshot = True,
                                        includeUnitsAvailable=False
                                       )
        body = response.body
        prices = body.get( 'prices', [] )
        for price in prices:
            self.process_price( price )

     The body of the response contains a prices attribute and a list of objects.https://oanda-api-v20.readthedocs.io/en/master/endpoints/pricing/pricinginfo.html Each item in the list is processed by the process_price() method.

Implementing the method for streaming prices

     Add the following stream_prices() method in the OandaBroker class to start streaming prices from your broker:

    def stream_prices( self, symbols=[] ):
        # https://github.com/oanda/v20-python/blob/master/src/v20/pricing.py
        # accountID : Account Identifier
        # instruments : List of Instruments to stream Prices for.
        # snapshot: Flag that enables/disables the sending of a pricing snapshot
        #           when initially connecting to the stream.
        response = self.stream_api.pricing.stream(
                                                    self.accountid,
                                                    instruments = ",".join(symbols),
                                                    snapshot = True
                                                 )
        # https://github.com/oanda/v20-python/blob/master/src/v20/response.py
        # response.parts() ==> call self.line_parser ==> class Parser() in the pricing.py
        for msg_type, msg in response.parts():
            if msg_type == "pricing.PricingHeartbeat":
                continue
            elif msg_type == "pricing.ClientPrice":
                self.process_price( msg )

     Since the host connection expects a continuous stream, the response object has a parts() method that listens for incoming data. The msg object is essentially a price object, which we can reuse with the process_price() method to notify the listeners of an incoming price event.

Implementing the method for sending market orders

     Add the following send_market_order() method in the OandaBroker class, which will send a market order to your broker:

    def send_market_order( self, symbol, quantity, is_buy ):
        # https://github.com/oanda/v20-python/blob/master/src/v20/order.py
        # def market( self, accountID, **kwargs ) 
        #    return self.create(
        #                        accountID,
        #                        order=MarketOrderRequest(**kwargs) ==>
        #                      ) # ==> response=self.ctx.request(request) ==> return response
        
        # https://github.com/oanda/v20-python/blob/f28192f4a31bce038cf6dfa302f5878bec192fe5/src/v20/order.py#L2185
        # A MarketOrderRequest specifies the parameters that may be set
        # when creating a Market Order.
        # class MarketOrderRequest(BaseEntity):
        
        # type : The type of the Order to Create. Must be set to "MARKET" when creating a Market Order.
        #    self.type = kwargs.get("type", "MARKET")
        
        # The Market Order's Instrument
        #    self.instrument = kwargs.get("instrument")
        
        # The quantity requested to be filled by the Market Order.
        # A posititive number of units results in a long Order, and
        # a negative number of units results in a short Order.        
        #    self.units = kwargs.get("units")
        response = self.api.order.market( self.accountid,
                                          units = abs(quantity)*(1 if is_buy else -1),
                                          instrument = symbol,
                                          type = 'MARKET',
                                        )
        # 201 : indicate a successful connection to the broker
        if response.status != 201:
            # the order event is triggered with 
            # an empty transaction ID along with a NOT_FILLED status, 
            # indicating that the order is incomplete.
            # def on_order_event(symbol, quantity, is_buy, transaction_id, status):
            self.on_order_event( symbol, quantity, is_buy, None, 'NOT_FILLED' )
            return
        
        # https://github.com/oanda/v20-python/blob/f28192f4a31bce038cf6dfa302f5878bec192fe5/src/v20/order.py#L3548
        # elif if str(response.status) == "201":
        # if jbody.get('orderCancelTransaction') is not None:
        #     parsed_body['orderCancelTransaction'] = ...
        # response.body = parsed_body
        body = response.body
        if 'orderCancelTransaction' in body:
            self.on_order_event( symbol, quantity, is_buy, None, 'NOT_FILLED' )
            return
        
        # if jbody.get('lastTransactionID') is not None:
        #     parsed_body['lastTransactionID'] = ...
        # https://github.com/oanda/v20-python/blob/master/src/v20/response.py
        # def get(self, field, status=None):
        #     value = self.body.get(field)
        transaction_id = body.get( 'lastTransactionID', None )
        self.on_order_event( symbol, quantity, is_buy, transaction_id, 'FILLED' )

     When the market() method of the v20 order library is called, the status of the response is expected to be 201 to indicate a successful connection to the broker. A further check on the response body is recommended for signs of error in the execution of our orders. In the case of a successful execution, the transaction ID and the details of the order are passed along to the listeners by calling the on_order_event() event handler. Otherwise, the order event is triggered with an empty transaction ID along with a NOT_FILLED status, indicating that the order is incomplete.

Implementing the method for fetching positions

     Add the following get_positions() method in the OandaBroker class, which will fetch all the available position information for a given account:https://oanda-api-v20.readthedocs.io/en/master/endpoints/positions/positionlist.html

{
  "positions": [
    {
      "short": {
        "units": "0",
        "resettablePL": "-272.6805",
        "unrealizedPL": "0.0000",
        "pl": "-272.6805"
      },
      "unrealizedPL": "0.0000",
      "long": {
        "units": "0",
        "resettablePL": "0.0000",
        "unrealizedPL": "0.0000",
        "pl": "0.0000"
      },
      "instrument": "EUR_GBP",
      "resettablePL": "-272.6805",
      "pl": "-272.6805"
    },
    {
      "short": {
        "unrealizedPL": "870.0000",
        "tradeIDs": [
          "2121",
          "2123"
        ],
        "resettablePL": "-13959.3000",
        "units": "-20",
        "averagePrice": "10581.5",
        "pl": "-13959.3000"
      },
      "unrealizedPL": "870.0000",
      "long": {
        "units": "0",
        "resettablePL": "404.5000",
        "unrealizedPL": "0.0000",
        "pl": "404.5000"
      },
      "instrument": "DE30_EUR",
      "resettablePL": "-13554.8000",
      "pl": "-13554.8000"
    },
    {
      "short": {
        "units": "0",
        "resettablePL": "0.0000",
        "unrealizedPL": "0.0000",
        "pl": "0.0000"
      },
      "unrealizedPL": "0.0000",
      "long": {
        "units": "0",
        "resettablePL": "-12.8720",
        "unrealizedPL": "0.0000",
        "pl": "-12.8720"
      },
      "instrument": "EUR_USD",
      "resettablePL": "-12.8720",
      "pl": "-12.8720"
    }
  ],
  "lastTransactionID": "2124"
}
    def get_positions(self):
        # https://github.com/oanda/v20-python/blob/master/src/v20/position.py
        # def list( self, accountID, **kwargs ):
        # List all Positions for an Account. 
        # The Positions returned are for 'every instrument' that
        # has had a position during the lifetime of an the Account.
        response = self.api.position.list( self.accountid )
        body = response.body
        positions = body.get( 'positions', [] )
        for position in positions:
            # The Position's Instrument.
            symbol = position.instrument
            # The unrealized profit/loss of all open Trades that contribute to this Position.
            unrealized_pnl = position.unrealizedPL
            # Profit/loss realized by the Position over the lifetime of the Account.
            pnl = position.pl
            # The details of the long side of the Position.
            long = position.long
            # The details of the short side of the Position.
            short = position.short
            
            if short.units:
                # def on_position_event(symbol, is_long, units, upnl, pnl):
                self.on_position_event( symbol, False, short.units, unrealized_pnl, pnl )
            elif long.units:
                self.on_position_event( symbol, True, long.units, unrealized_pnl, pnl )
            else:
                self.on_position_event( symbol, None, 0, unrealized_pnl, pnl )

     In the response body, the position property contains a list of position objects, each having attributes for the contract symbol(instrument), the unrealized and realized gains or losses(unrealizedPL and pl), and the number of long and short positions. This information is passed along to the listeners through the on_position_event() event handler.

import v20

class OandaBroker( Broker ):
    # http://developer.oanda.com/rest-live-v20/development-guide/
    
    # REST API
    # 120 requests per second. Excess requests will receive HTTP 429 error. 
    # This restriction is applied against the requesting IP address.
    PRACTICE_API_HOST = 'api-fxpractice.oanda.com'       # fxTrade Practice
    # Stream API
    # 20 active streams. Requests above this threshold will be rejected. 
    # This restriction is applied against the requesting IP address.
    PRACTICE_STREAM_HOST = 'stream-fxpractice.oanda.com' # fxTrade Practice
    
    # REST API
    LIVE_API_HOST = 'api-fxtrade.oanda.com'              # fxTrade
    # Stream API
    LIVE_STREAM_HOST = 'stream-fxtrade.oanda.com'        # fxTrade
    
    PORT = '443'
    
    def __init__( self, accountid, token, is_live = False ):
    
        if is_live:
            host = self.LIVE_API_HOST
            stream_host = self.LIVE_STREAM_HOST
        else:
            host = self.PRACTICE_API_HOST
            stream_host = self.PRACTICE_STREAM_HOST
        
        super( OandaBroker, self ).__init__(host, self.PORT)
        
        self.accountid = accountid
        self.token = token
        
        # https://github.com/oanda/v20-python/blob/master/src/v20/__init__.py
        self.api = v20.Context( host,       # hostname: The hostname of the v20 REST server 
                                self.port,  # port: The port of the v20 REST server
                                token=token 
                              )# token: The authorization token to use when making requests to the v20 server
        self.stream_api = v20.Context( stream_host, self.port, token=token )
    
    def process_price( self, price ):
        # The price object contains an instrument property of a string object, 
        symbol = price.instrument
        
        if not symbol:
            print( 'Price symbol is empty!' )
            return
         
        # along with list objects in the bids and asks properties. 
        # Typically, Level 1 quotes are available, 
        # so we read the first item of the each list.
        # Each item in the list is a price_bucket object, 
        # from which we extract the bid and ask price.
        
        bids = price.bids or []
        price_bucket_bid = bids[0] if bids and len(bids) > 0 else None
        bid = price_bucket_bid.price if price_bucket_bid else 0
        
        asks = price.asks or []
        price_bucket_ask = asks[0] if asks and len(asks) > 0 else None
        ask = price_bucket_ask.price if price_bucket_ask else 0
        
        self.on_price_event( symbol, bid, ask )
    
    def get_prices( self, symbols=[] ):    
    # accountID: Account Identifier
    # instruments: List of Instruments to stream Prices for.
    # snapshot: Flag that enables/disables the sending of a pricing snapshot
    #           when initially connecting to the stream.
    # includeUnitsAvailable: Flag that enables the inclusion of the
    #                        unitsAvailable field in the returned Price objects
    # https://oanda-api-v20.readthedocs.io/en/master/endpoints/pricing/pricinginfo.html
    # "unitsAvailable": {
    #                     "default": {
    #                                  "short": "506246",
    #                                  "long": "506128"
    #                                },
    #                     "reduceOnly": {
    #                                     "short": "0",
    #                                     "long": "0"
    #                                   },
    #                      "openOnly": {
    #                                    "short": "506246",
    #                                    "long": "506128"
    #                                  },
    #                      "reduceFirst": {
    #                                       "short": "506246",
    #                                       "long": "506128"
    #                                     }
    #                   }
    # https://github.com/oanda/v20-python/blob/master/src/v20/pricing.py
        response = self.api.pricing.get(
                                        self.accountid,
                                        # "instruments": "EUR_USD,EUR_JPY"
                                        instruments = ",".join( symbols ),
                                        snapshot = True,
                                        includeUnitsAvailable=False
                                       )
        body = response.body
        prices = body.get( 'prices', [] )
        for price in prices:
            self.process_price( price )
    
    def stream_prices( self, symbols=[] ):
        # https://github.com/oanda/v20-python/blob/master/src/v20/pricing.py
        # accountID : Account Identifier
        # instruments : List of Instruments to stream Prices for.
        # snapshot: Flag that enables/disables the sending of a pricing snapshot
        #           when initially connecting to the stream.
        response = self.stream_api.pricing.stream(
                                                    self.accountid,
                                                    instruments = ",".join(symbols),
                                                    snapshot = True
                                                 )
        # https://github.com/oanda/v20-python/blob/master/src/v20/response.py
        # response.parts() ==> call self.line_parser ==> class Parser() in the pricing.py
        for msg_type, msg in response.parts():
            if msg_type == "pricing.PricingHeartbeat":
                continue
            elif msg_type == "pricing.ClientPrice":
                self.process_price( msg )
            
    def send_market_order( self, symbol, quantity, is_buy ):
        # https://github.com/oanda/v20-python/blob/master/src/v20/order.py
        # def market( self, accountID, **kwargs ) 
        #    return self.create(
        #                        accountID,
        #                        order=MarketOrderRequest(**kwargs) ==>
        #                      ) # ==> response=self.ctx.request(request) ==> return response
        
        # https://github.com/oanda/v20-python/blob/f28192f4a31bce038cf6dfa302f5878bec192fe5/src/v20/order.py#L2185
        # A MarketOrderRequest specifies the parameters that may be set
        # when creating a Market Order.
        # class MarketOrderRequest(BaseEntity):
        
        # type : The type of the Order to Create. Must be set to "MARKET" when creating a Market Order.
        #    self.type = kwargs.get("type", "MARKET")
        
        # The Market Order's Instrument
        #    self.instrument = kwargs.get("instrument")
        
        # The quantity requested to be filled by the Market Order.
        # A posititive number of units results in a long Order, and
        # a negative number of units results in a short Order.        
        #    self.units = kwargs.get("units")
        response = self.api.order.market( self.accountid,
                                          units = abs(quantity)*(1 if is_buy else -1),
                                          instrument = symbol,
                                          type = 'MARKET',
                                        )
        # 201 : indicate a successful connection to the broker
        if response.status != 201:
            # the order event is triggered with 
            # an empty transaction ID along with a NOT_FILLED status, 
            # indicating that the order is incomplete.
            # def on_order_event(symbol, quantity, is_buy, transaction_id, status):
            self.on_order_event( symbol, quantity, is_buy, None, 'NOT_FILLED' )
            return
        
        # https://github.com/oanda/v20-python/blob/f28192f4a31bce038cf6dfa302f5878bec192fe5/src/v20/order.py#L3548
        # elif if str(response.status) == "201":
        # if jbody.get('orderCancelTransaction') is not None:
        #     parsed_body['orderCancelTransaction'] = ...
        # response.body = parsed_body
        body = response.body
        if 'orderCancelTransaction' in body:
            self.on_order_event( symbol, quantity, is_buy, None, 'NOT_FILLED' )
            return
        
        # if jbody.get('lastTransactionID') is not None:
        #     parsed_body['lastTransactionID'] = ...
        # https://github.com/oanda/v20-python/blob/master/src/v20/response.py
        # def get(self, field, status=None):
        #     value = self.body.get(field)
        transaction_id = body.get( 'lastTransactionID', None )
        self.on_order_event( symbol, quantity, is_buy, transaction_id, 'FILLED' )
        
    def get_positions(self):
        # https://github.com/oanda/v20-python/blob/master/src/v20/position.py
        # def list( self, accountID, **kwargs ):
        # List all Positions for an Account. 
        # The Positions returned are for 'every instrument' that
        # has had a position during the lifetime of an the Account.
        response = self.api.position.list( self.accountid )
        body = response.body
        positions = body.get( 'positions', [] )
        for position in positions:
            # The Position's Instrument.
            symbol = position.instrument
            # The unrealized profit/loss of all open Trades that contribute to this Position.
            unrealized_pnl = position.unrealizedPL
            # Profit/loss realized by the Position over the lifetime of the Account.
            pnl = position.pl
            # The details of the long side of the Position.
            long = position.long
            # The details of the short side of the Position.
            short = position.short
            
            if short.units:
                # def on_position_event(symbol, is_long, units, upnl, pnl):
                self.on_position_event( symbol, False, short.units, unrealized_pnl, pnl )
            elif long.units:
                self.on_position_event( symbol, True, long.units, unrealized_pnl, pnl )
            else:
                self.on_position_event( symbol, None, 0, unrealized_pnl, pnl )

Getting the prices

     With the methods of broker now defined, we can test the connection that is set up between our broker by reading the current market prices. The Broker class may be instantiated using the following Python codes:https://www.oanda.com/demo-account/

 https://www.oanda.com/demo-account/tpa/personal_token

https://trade.oanda.com/

# Replace these 2 values with your own!
ACCOUNT_ID = '101-001-23023947-001'
API_TOKEN = '5abdaad02938a76a10164a78a37d3cfc-87fabd8764bcb251a45fecb285a9cc63'

broker = OandaBroker( ACCOUNT_ID, API_TOKEN )

     Replace the two constant variables, ACCOUNT_ID and API_TOKEN , with your own credentials given by your broker, which identifies your own trading account. The broker variable is an instance of OandaBroker , which we can use to perform various broker-specific calls.

     Suppose that we are interested in finding out the current market price of the EUR/USD currency pair. Let's define a constant variable to hold the symbol of this instrument that is recognized by our broker:

SYMBOL = 'EUR_USD'

Next, define a price event listener coming from our broker, using the following code:

import datetime as dt

def on_price_event(symbol, bid, ask):
    print( dt.datetime.now(), 
           '\n[PRICE]',
           symbol, '\nbid:', bid, '\nask:', ask
         )

broker.on_price_event = on_price_event

     The on_price_event()function is defined as the listener for incoming price information and is assigned to the broker. on_price_event event handler. We expect three values from a pricing event – the contractsymbol, the bid price, and the ask price – which we simply print to the console.

The get_prices() method is called to fetch the current market price from our broker:

# def get_prices( self, symbols=[] ):
broker.get_prices( symbols=[SYMBOL] )

Sending a simple market order

     In the same way that we use for fetching prices, we can reuse the broker variable to send a market order to our broker.

     Now suppose that we are interested in buying 1 unit of the same EUR/USD currency pair; the following code performs this action:

def on_order_event( symbol, quantity, is_buy, transaction_id, status ):
    print( dt.datetime.now(), '[ORDER]',
           '\ntransaction_id:', transaction_id,
           '\nstatus:', status,
           '\nsymbol:', symbol,
           '\nquantity:', quantity,
           '\nis_buy:', is_buy,
         )
broker.on_order_event = on_order_event

# def send_market_order( self, symbol, quantity, is_buy ):
# ...
#    self.on_order_event( symbol, quantity, is_buy, transaction_id, 'FILLED' )
broker.send_market_order( SYMBOL, 1, True )

 
https://www.oanda.com/demo-account/transaction-history/

Getting position updates

     With a long position opened by sending a market order to buy, we should be able to view our current EUR/USD position. We can do so on the broker object using the following code: 

def on_position_event( symbol, is_long, units, upnl, pnl ):
    print( dt.datetime.now(), '\n[POSITION]',
           'symbol:', symbol,
           '\nis_long:', is_long,
           '\nunits:', units,
           '\nupnl:', upnl,
           '\npnl:', pnl
         )
broker.on_position_event = on_position_event

# def get_positions(self):
# ...
#    self.on_position_event( symbol, True, long.units, unrealized_pnl, pnl )
broker.get_positions()

     The on_position_event() function is defined as the listener for incoming position updates from our broker and is assigned to the broke.on_position_event event handler. When the get_positions() method is called, the broker returns the position information and triggers the following output:
https://www.oanda.com/demo-account/transaction-history/

https://trade.oanda.com/

Building a mean-reverting algorithmic trading system

     With our broker now accepting orders and responding to our requests, we can begin to design a fully-automated trading system. In this section, we will explore how to design and implement a mean-reverting algorithmic trading system.

Designing the mean-reversion algorithm

     Suppose we believe that in normal market conditions, prices fluctuate, but tend to revert back to some short-term level, such as the average of the most recent prices. In this example, we assume that the EUR/USD currency pair is exhibiting a mean-reversion property in the near short-term period. First, we resample the raw tick-level data into standard time series intervals, for example, at one-minute intervals. Then, taking a number of the most recent periods for calculating the short-term average price (for example, with five periods), we are saying that we believe the EUR/USD prices will revert toward the average of the prior five minutes' prices

  • As soon as the bidding price of the EUR/USD currency pairexceedsthe short-term average price, with five minutes as our example, our trading system shall generate a sell signal, and we can choose to enter into a short position with a sell market order.
  • Likewise, when the asking price of EUR/USD falls below the average price, a buy signal is generated and we can choose to enter into a long position witha buy market order.

     The moment that a position is opened, we may use the same signals to close out our position.

  • When a long position is opened, we close our position on a sell signal by entering an order to sell at the market.
  • Likewise, when a short position is opened, we close our position on a buy signal by entering an order to buy at the market.

     You might observe that there are plenty of flaws in our trading strategy. Closing out our position does not guarantee a profit. Our belief of the market can be wrong; in adverse market conditions, a signal might remain in one direction for some time and there is a high probability of closing out our position at a huge loss! As a trader, you should figure out a personal trading strategy that suits your beliefs and risk appetite.

Implementing the mean-reversion trader class

     The resample interval and the number of periods in our calculation are two important parameters that are required by our trading system. First, create a class named MeanReversionTrader, which we can instantiate and run as our trading system:

import datetime as dt
import pandas as pd

class MeanReversionTrader( object ):
    def __init__( self, broker, symbol=None, units=1,
                  resample_interval='60s', mean_periods=5
                ):
        """
        A trading platform that trades on 'one side'
            based on a mean-reverting algorithm.
            
        : param broker : Broker object
        : param symbol : A str object recognized by the broker for trading
        : param units : Number of units to trade
        : param resample_interval :
            Frequency for resampling price time series
        : param mean_periods: Number of resampled intervals
            for calculating the average price            
        """
        # The setup_broker() method call sets up our class 
        # to handle events from our broker object
        self.broker = self.setup_broker( broker )
        
        self.resample_interval = resample_interval
        # the number of periods for our mean calculation.
        self.mean_periods = mean_periods
        self.symbol = symbol
        self.units = units
        
        # to store received price data in self.df_prices
        self.df_prices = pd.DataFrame( columns=[symbol] )
        self.pnl=0  # realized Profit and Loss
        self.upnl=0 # unrealized Profit and Loss
        
        # The latest bid and ask prices
        self.bid_price=0
        self.ask_price=0
        self.position = 0 # the number of units of our current position
        # whether an order is pending execution by our broker
        self.is_order_pending = False
        # whether the current trading state cycle is open
        self.is_next_signal_cycle = True

     The five parameters in our constructor initialize the state of our trading system

  • the broker used,
  • the symbol to trade,
  • the number of units to trade,
  • the resampling interval of our price data,
  • and the number of periods for our mean calculation.

These values are simply stored as class variables.

      The setup_broker() method call sets up our class to handle events from our broker object, which we will define shortly. As we receive price data, these are stored in a pandas DataFrame variable, df_prices. The latest bid and ask prices are stored in the bid_price and ask_price variables for calculating signals. The mean variable will store the calculated mean of the prior number of mean_period prices. The position variable will store the number of units of our current position.

  • A negative value indicates a short position,
  • and a positive value indicates a long position.

     The is_order_pending Boolean flag indicates whether an order is pending execution by our broker, and the is_next_signal_cycle Boolean flag indicates whether the current trading state cycle is open. Note that our system states can be as follows: 

  1. Wait for a buy or sell signal.
  2. Place an order on a buy or sell signal.
  3. When a position is opened, wait for a sell or buy signal.
  4. Place an order on a sell or buy signal.
  5. When the position is closed, go to step 1. 

     For every cycle of steps from 1 to 5, we will only trade one unit. These Boolean flags act as a lock to prevent multiple orders from entering into the system at any one time

Writing the mean-reversion signal generators

     We want our decision-making algorithm to recalculate trading signals on every price or orderupdate. Let's create a generate_signals_and_think() method inside the MeanReversionTrader class to do this: 

    def generate_signals_and_think(self):
        # ffill: propagate last valid observation forward to next valid
        df_resampled = self.df_prices.resample( self.resample_interval )\
                                     .ffill()\
                                     .dropna()
        resampled_len = len( df_resampled.index )
        
        if resampled_len < self.mean_periods:
            print( 'Insufficient data size to calculate logic. Need',
                   self.mean_periods - resampled_len,
                   'more.'
                 )
            print("Please wait...")
            return
        # df_resampled=(bid+ask)/2 ==> .tail( self.mean_periods ).mean()
        mean = df_resampled.tail( self.mean_periods ).mean()[self.symbol]
        
        # Signal flag calculation
        is_signal_buy = mean > self.ask_price
        is_signal_sell = mean < self.bid_price
        
        print( 'is_signal_buy:', is_signal_buy,
               'is_signal_sell:', is_signal_sell,
               'average_price: %.5f' % mean,
               'bid:', self.bid_price,
               'ask:', self.ask_price
             )
        self.think( is_signal_buy, is_signal_sell )

     Since price data are stored in the df_prices variable as a pandas DataFrame, we can resample them at regular intervals, as defined by the resample_interval variable given in the constructor. The ffill() method forward-fills any missing data and the dropna() command removes the first missing value after resampling. There must be sufficient data available for calculating the mean, otherwise this method simply exits. The mean_periods variable represents the minimum length of resampled data that must be available.

     The tail(self.mean_periods) method takes the most recent resampled intervals(resample_interval) and calculates the average using the mean() method, resulting in another pandas DataFrame. The mean level is taken by index referencing the column of the DataFrame, which is simply the instrument symbol.

     Using the average price available for the mean-reversion algorithm, we can generate the buy and sell signals. Here,

  • a buy signal is generated when the average price is greater than the market asking price(The term "ask" refers to the lowest price at which a seller will sell the stock.), and
  • a sell signal is generated when the average price is lower than the market bidding price.(The term "bid" refers to the highest price a buyer will pay to buy a specified number of shares of a stock at any given time.)
  • Our short-term belief is that market prices will revert to the average price.

     After printing these calculated values to the console for better debugging, we can now make use of the buy and sell signals to perform actual tradesin a separate method named think() inside the same class:

    def think( self, is_signal_buy, is_signal_sell ):
        if self.is_order_pending:
            # you might want to add your own logic to handle orders that 
            # have stayed in the pending state for too long 
            # and try another strategy
            return
        
        if self.position == 0:
            self.think_when_position_flat( is_signal_buy, is_signal_sell )
        elif self.position > 0:
            self.think_when_position_long( is_signal_sell )
        elif self.position < 0:
            self.think_when_position_short( is_signal_buy )

     If an order is still in pending state by a broker, we simply do nothing and exit the method. Since market conditions may change at any time, you might want to add your own logic to handle orders that have stayed in the pending state for too long and try another strategy

     The three if-else statements handles the trading logic when our position is flat, long, or short, respectively. When our position is flat, the think_when_position_flat() method is called, written as the following:

    def think_when_position_flat( self, is_signal_buy, is_signal_sell ):
        # is_signal_buy = mean > self.ask_price
        if is_signal_buy and self.is_next_signal_cycle:
            print( 'Opening position, BUY',
                   self.symbol, self.units, 'units'
                 )
            self.is_order_pending = True
            # def send_market_order( self, symbol, quantity, is_buy ):
            self.send_market_order( self.symbol, self.units, True )
            return
        
        # is_signal_sell = mean < self.bid_price
        if is_signal_sell and self.is_next_signal_cycle:
            print( 'Opening position, SELL',
                   self.symbol, self.units, 'units'
                 )
            self.is_order_pending = True
            self.send_market_order( self.symbol, self.units, False )
            return
        
        # self.bid_price <= mean <= self.ask_price
        # wait for a buy or sell signal
        if not is_signal_buy and not is_signal_sell:
            self.is_next_signal_cycle = True
  • The first if statement handles the condition that, upon a buy signal and when the current trading cycle is open(self.is_next_signal_cycle == True), we enter into a long position by sending a market order to buy and mark that order as pending.
  • Conversely, the second if statement handles the condition to enter into a short position upon a sell signal.
  • Otherwise, since the position is flat with neither a buy nor sell signal (self.bid_price(buyer will pay to buy) <= mean <= self.ask_price(a seller will sell) ), we simply set the is_next_signal_cycle to True(self.is_next_signal_cycle == True) until a signal becomes available. 

     When we are in a long position, the think_when_position_long() method is called, written as the following:

    def think_when_position_long( self, is_signal_sell ):
        # is_signal_sell = mean < self.bid_price
        if is_signal_sell:
            print( 'Closing position, SELL',
                   self.symbol, self.units, 'units'
                 )
            self.is_order_pending = True
            self.send_market_order( self.symbol, self.units, False )

     On a sell signal (is_signal_sell = mean < self.bid_price(buyer will pay to buy) ), we mark the order as pending and close out our long position immediately by sending a market order to sell.

     Similarly, when we are in a short position, the think_when_position_short() method is called, written as the following:

    def think_when_position_short( self, is_signal_buy ):
        # is_signal_buy = mean > self.ask_price
        if is_signal_buy:
            print( 'Closing position, BUY',
                   self.symbol, self.units, 'units'
                 )
            self.is_order_pending = True
            self.send_market_order( self.symbol, self.units, True )

     On a buy signal (is_signal_buy = mean > self.ask_price(a seller will sell)), we mark the order as pending and close out our short position immediately by sending a market order to buy

     To perform the order routing functionality, add the following send_market_order() class method to our MeanReversionTrader class:

    def send_market_order( self, symbol, quantity, is_buy ):
        print("########## send market order ##########")
        self.broker.send_market_order( symbol, quantity, is_buy )

The order information is simply forwarded to our Broker class for execution. 

Adding event listeners

     Let's hook up the price, order, and position events in our MeanReversionTrader class.

Add the setup_broker() method into this class, as follows:

    def setup_broker( self, broker ):
        broker.on_price_event = self.on_price_event
        broker.on_order_event = self.on_order_event
        broker.on_position_event = self.on_position_event
        
        return broker

     We are simply assigning three class methods as listeners on any broker-generated event to listen to price, order, and position updates.

get the current position

     Write the get_position() method to retrieve the up-to-date position information from our broker, as follows:

    def get_positions( self ):
        try:
            self.broker.get_positions()
        except Exception as ex:
            print('get_positions error:', ex)

on_price_event()

Add the on_price_event() method into this class, as follows:

    def on_price_event( self, symbol, bid, ask ):
        print( dt.datetime.now(), '[PRICE]', 
               ', Symbol:' , symbol,
               ', Bid:', bid, 
               ', Ask:', ask
             )
        self.bid_price = bid
        self.ask_price = ask
        self.df_prices.loc[pd.Timestamp.now(), # index
                           symbol              # column
                          ] = (bid+ask)/2.
        
        self.get_positions() # Update positions before thinking
        
        self.generate_signals_and_think()
        
        self.print_state()

     When a price event is received(stream_prices() ==>process_price() ==>on_price_event() ), we store them in our bid_price , ask_price , and df_prices class variables. As the price changes, so do our open positions and signal values.

  • The get_position() method call retrieves up-to-date information on our positions, and
  • the generate_signals_and_think()call recalculates our signals and decides whether to make the trade.
  • The current state of the system is printed to the console using the print_state() command.

print_state()

Add the print_state() method into our class, as follows: 

    @property
    def position_state( self ):
        if self.position == 0:
            return 'FLAT'
        elif self.position > 0:
            return 'LONG'
        elif self.position < 0:
            return 'SHORT'
        
    def print_state( self ):
        print( dt.datetime.now(), 
               'Symbol:',self.symbol, 
               self.position_state, abs(self.position), 
               ', upnl:', self.upnl, 
               ', pnl:', self.pnl 
             )

     As soon as there are any updates to our orders, positions, or market prices, we print the latest state of our system to the console.

on_order_event()

Add the on_order_event() method into our class, as follows:

    def on_order_event( self, symbol, quantity, is_buy, 
                        transaction_id, status 
                      ):
        print( "########## Order Processing result ##########" )
        print( dt.datetime.now(), '[ORDER]',
               ', Transaction_id:', transaction_id,
               ', Status:', status,
               ', Symbol:', symbol,
               ', Quantity:', quantity,
               ', is_buy:', is_buy,
             )
        
        if status == 'FILLED':
            self.is_order_pending = False
            self.is_next_signal_cycle = False
            
            print('After Filled, the current position:')
            self.get_positions() # Update positions before thinking
            # self.generate_signals_and_think() #########

     When an order event is received( run() ==> stream_prices() ==>process_price() ==>on_price_event() ==>self.generate_signals_and_think() ==>think(), {flat,long,short}, perform decision-making ==>  send_market_order( self, symbol, quantity, is_buy ) ==> on_order_event() ), we print them out to the console. In our broker's on_order_event implementation, an order that is executed successfully will pass either a status value of FILLED or NOT_FILLED . Only on a successful order can we turn off our Boolean locks, retrieve our latest position, and perform decision-making for closing out our position.

on_position_event()

Add the on_position_event() method into our class, as follows:

    def on_position_event( self, symbol, is_long, units, upnl, pnl ):
        if symbol == self.symbol:
            self.position = abs(units) * (1 if is_long else -1)
        self.pnl = pnl
        self.upnl = upnl
        self.print_state()

     When a position update event is received ( on_order_event() ==> get_positions() ==> on_position_event() ) for our intended trade symbol, we store our position information, the realized gains, and unrealized gains. The current state of the system is printed to the console using the print_state() command.

###################################################

def on_order_event( symbol, quantity, is_buy, transaction_id, status ):
    print( dt.datetime.now(), '[ORDER]',
           '\ntransaction_id:', transaction_id,
           '\nstatus:', status,
           '\nsymbol:', symbol,
           '\nquantity:', quantity,
           '\nis_buy:', is_buy,
         )
broker.on_order_event = on_order_event

# def send_market_order( self, symbol, quantity, is_buy ):
# ...
#    self.on_order_event( symbol, quantity, is_buy, transaction_id, 'FILLED' )
broker.send_market_order( SYMBOL, 1, False )

def on_position_event( symbol, is_long, units, upnl, pnl ):
    print( dt.datetime.now(), '\n[POSITION]',
           'symbol:', symbol,
           '\nis_long:', is_long,
           '\nunits:', units,
           '\nupnl:', upnl,
           '\npnl:', pnl
         )
broker.on_position_event = on_position_event

# def get_positions(self):
# ...
#    self.on_position_event( symbol, True, long.units, unrealized_pnl, pnl )
broker.get_positions()

###################################################

Running our trading system

     Finally, to start running our trading system, we need an entry point. Add the following run() class method to the MeanReversionTrader class:

    def run( self ):
        self.broker.stream_prices( symbols = [self.symbol] )

     During the first run of our trading system, we read our current positions and use the information to initialize all position-related information. Then, we request our broker to start streaming prices for the given symbol and hold the connection until the program is terminated.

     With an entry point defined, all we need to do is initialize our MeanReversionTrader class and call the run() command using the following codes:

trader = MeanReversionTrader( broker,
                              resample_interval='60s',
                              symbol='EUR_USD',
                              units=1
                            )
trader.run()

     Remember that the broker variable contains an instance of the OandaBroker class as defined from the previous Getting prices section, and we can reuse it for this class. Our trading system will use this broker object to perform broker-related calls. We are interested in the EUR/USD currency pair, trading one unit at each time. The resample_interval variable with a value of 60s states that our stored prices are to be resampled at one-minute intervals. The mean_periods variable with a value of 5 states that we will take the average of the most recent five intervals, or the average price of the past five minutes.

     To start our trading system, make the call to run() ; pricing updates will start trickling in, enabling our system to trade on its own. You should see an output on the console that is similar to the following:

     From the output, it looks as though our position is currently flat, and there is insufficient pricing data for calculating our trading signals.

P&LUnrealized or P&LRealized

https://www1.oanda.com/resources/legal/united-states/legal/margin-rules

https://blog.csdn.net/Linli522362242/article/details/121896073

     After several minutes, when there is sufficient data for a trading signal calculation, we should be able to observe the following outcome:
https://www.oanda.com/demo-account/transaction-history/

     The average price is 1.00416. Since the current market bidding price for EUR/USD is 1.00418, more than the average price, a sell signal is generated (based on mean reversion) . A sell market order is generated to open a short position in EUR/USD of one unit. This leads to an realized loss of $0002. 

#############

     The average price is 1.00416. Since the current market asking price for EUR/USD is 1.00414, less than the average price, a buy signal is generated. A buy market order is generated to open a long position in EUR/USD of one unit. This leads to an unrealized loss of $0, and the realized loss is 0.0537

     Let the system run on its own for a while, and it should be able to close out the positions on its own. To stop trading, terminate the running process using Ctrl + Z or something similar. Remember to manually close out any remaining trading positions once the program stops running. You now have a fully functional and automated trading system! 

     The system design and trading parameters here are stated as an example and don't necessarily lead to positive outcomes!You should experiment with various trading parameters and improve the handling of events to figure out the optimal strategy for your trading plan

def on_order_event( symbol, quantity, is_buy, transaction_id, status ):
    print( dt.datetime.now(), '[ORDER]',
           '\ntransaction_id:', transaction_id,
           '\nstatus:', status,
           '\nsymbol:', symbol,
           '\nquantity:', quantity,
           '\nis_buy:', is_buy,
         )
broker.on_order_event = on_order_event

# def send_market_order( self, symbol, quantity, is_buy ):
# ...
#    self.on_order_event( symbol, quantity, is_buy, transaction_id, 'FILLED' )
broker.send_market_order( SYMBOL, 1, True )

def on_position_event( symbol, is_long, units, upnl, pnl ):
    print( dt.datetime.now(), '\n[POSITION]',
           'symbol:', symbol,
           '\nis_long:', is_long,
           '\nunits:', units,
           '\nupnl:', upnl,
           '\npnl:', pnl
         )
broker.on_position_event = on_position_event

# def get_positions(self):
# ...
#    self.on_position_event( symbol, True, long.units, unrealized_pnl, pnl )
broker.get_positions()

Full code:

import datetime as dt
import pandas as pd

class MeanReversionTrader( object ):
    def __init__( self, broker, symbol=None, units=1,
                  resample_interval='60s', mean_periods=5
                ):
        """
        A trading platform that trades on 'one side'
            based on a mean-reverting algorithm.
            
        : param broker : Broker object
        : param symbol : A str object recognized by the broker for trading
        : param units : Number of units to trade
        : param resample_interval :
            Frequency for resampling price time series
        : param mean_periods: Number of resampled intervals
            for calculating the average price            
        """
        # The setup_broker() method call sets up our class 
        # to handle events from our broker object
        self.broker = self.setup_broker( broker )
        
        self.resample_interval = resample_interval
        # the number of periods for our mean calculation.
        self.mean_periods = mean_periods
        self.symbol = symbol
        self.units = units
        
        # to store received price data in self.df_prices
        self.df_prices = pd.DataFrame( columns=[symbol] )
        self.pnl=0  # realized Profit and Loss
        self.upnl=0 # unrealized Profit and Loss
        
        # The latest bid and ask prices
        self.bid_price=0
        self.ask_price=0
        self.position = 0 # the number of units of our current position
        # whether an order is pending execution by our broker
        self.is_order_pending = False
        # whether the current trading state cycle is open
        self.is_next_signal_cycle = True
        
    def setup_broker( self, broker ):
        broker.on_price_event = self.on_price_event
        broker.on_order_event = self.on_order_event
        broker.on_position_event = self.on_position_event
        
        return broker
    
    def get_positions( self ):
        try:
            self.broker.get_positions()
        except Exception as ex:
            print('get_positions error:', ex)
            
    def send_market_order( self, symbol, quantity, is_buy ):
        print("########## send market order ##########")
        self.broker.send_market_order( symbol, quantity, is_buy )
    
    def think_when_position_flat( self, is_signal_buy, is_signal_sell ):
        # is_signal_buy = mean > self.ask_price
        if is_signal_buy and self.is_next_signal_cycle:
            print( 'Opening position, BUY',
                   self.symbol, self.units, 'units'
                 )
            self.is_order_pending = True
            # def send_market_order( self, symbol, quantity, is_buy ):
            self.send_market_order( self.symbol, self.units, True )
            return
        
        # is_signal_sell = mean < self.bid_price
        if is_signal_sell and self.is_next_signal_cycle:
            print( 'Opening position, SELL',
                   self.symbol, self.units, 'units'
                 )
            self.is_order_pending = True
            self.send_market_order( self.symbol, self.units, False )
            return
        
        # self.bid_price <= mean <= self.ask_price
        # wait for a buy or sell signal
        if not is_signal_buy and not is_signal_sell:
            self.is_next_signal_cycle = True
            
    def think_when_position_long( self, is_signal_sell ):
        # is_signal_sell = mean < self.bid_price
        if is_signal_sell:
            print( 'Closing position, SELL',
                   self.symbol, self.units, 'units'
                 )
            self.is_order_pending = True
            self.send_market_order( self.symbol, self.units, False )
            
    def think_when_position_short( self, is_signal_buy ):
        # is_signal_buy = mean > self.ask_price
        if is_signal_buy:
            print( 'Closing position, BUY',
                   self.symbol, self.units, 'units'
                 )
            self.is_order_pending = True
            self.send_market_order( self.symbol, self.units, True )
    
    def think( self, is_signal_buy, is_signal_sell ):
        print('Decision:')
        if self.is_order_pending:
            # you might want to add your own logic to handle orders that 
            # have stayed in the pending state for too long 
            # and try another strategy
            return
        
        if self.position == 0:
            self.think_when_position_flat( is_signal_buy, is_signal_sell )
        elif self.position > 0:
            self.think_when_position_long( is_signal_sell )
        elif self.position < 0:
            self.think_when_position_short( is_signal_buy )
            
    def generate_signals_and_think(self):
        # ffill: propagate last valid observation forward to next valid
        df_resampled = self.df_prices.resample( self.resample_interval )\
                                     .ffill()\
                                     .dropna()
        resampled_len = len( df_resampled.index )
        
        if resampled_len < self.mean_periods:
            print( 'Insufficient data size to calculate logic. Need',
                   self.mean_periods - resampled_len,
                   'more.'
                 )
            print("Please wait...")
            return
        # df_resampled=(bid+ask)/2 ==> .tail( self.mean_periods ).mean()
        mean = df_resampled.tail( self.mean_periods ).mean()[self.symbol]
        
        # Signal flag calculation
        is_signal_buy = mean > self.ask_price
        is_signal_sell = mean < self.bid_price
        
        print( 'is_signal_buy:', is_signal_buy,
               'is_signal_sell:', is_signal_sell,
               'average_price: %.5f' % mean,
               'bid:', self.bid_price,
               'ask:', self.ask_price
             )
        self.think( is_signal_buy, is_signal_sell )
        
    @property
    def position_state( self ):
        if self.position == 0:
            return 'FLAT position ='
        elif self.position > 0:
            return 'LONG position ='
        elif self.position < 0:
            return 'SHORT position ='
        
    def print_state( self ):
        print( dt.datetime.now(), 
               'Symbol:',self.symbol, 
               self.position_state, self.position, 
               ', upnl:', self.upnl, 
               ', pnl:', self.pnl 
             )
        
    def on_price_event( self, symbol, bid, ask ):
        print( dt.datetime.now(), '[PRICE]', 
               ', Symbol:' , symbol,
               ', Bid:', bid, 
               ', Ask:', ask
             )
        self.bid_price = bid
        self.ask_price = ask
        self.df_prices.loc[pd.Timestamp.now(), # index
                           symbol              # column
                          ] = (bid+ask)/2.
        
        self.get_positions() # Update positions before thinking
        
        self.generate_signals_and_think()        
    
    def on_order_event( self, symbol, quantity, is_buy, 
                        transaction_id, status 
                      ):
        print( "########## Order Processing result ##########" )
        print( dt.datetime.now(), '[ORDER]',
               ', Transaction_id:', transaction_id,
               ', Status:', status,
               ', Symbol:', symbol,
               ', Quantity:', quantity,
               ', is_buy:', is_buy,
             )
        
        if status == 'FILLED':
            self.is_order_pending = False
            self.is_next_signal_cycle = False
            
            print('After Filled, the current position:')
            self.get_positions() # Update positions before thinking
            # self.generate_signals_and_think() #########
            
    def on_position_event( self, symbol, is_long, units, upnl, pnl ):
        if symbol == self.symbol:
            self.position = abs(units) * (1 if is_long else -1)
        self.pnl = pnl
        self.upnl = upnl
        self.print_state()
   
    def run( self ):
        self.broker.stream_prices( symbols = [self.symbol] )
trader = MeanReversionTrader( broker,
                              resample_interval='60s',
                              symbol='EUR_USD',
                              units=1
                            )
trader.run()

Building a trend-following trading platform

     In the previous section, we followed the steps for building a mean-reverting trading platform. The same functionality can be easily extended to incorporate any other trading strategies. In this section, we will take a look at reusing the MeanReversionTrader class to implement a trend-following trading system.

Designing the trend-following algorithm

     Suppose that this time, we believe that the current market conditions exhibit a trend-following pattern, perhaps due to seasonal changes, economic projections, or government policy. As prices fluctuate, and as the short-term average price level crosses the average long-term price level by a certain threshold, we generate a buy or sell signal. 

  • First, we resample raw tick-level data into standard time series intervals, for example, at one-minute intervals.
  • Second, taking a number of the most recent periods, for example, with five periods, we calculate the short-term average price for the past five minutes.
  • Finally, taking a larger number of the most recent periods, for example, with ten periods, we calculate the long-term average price for the past ten minutes.

     In a market with no movement, the average short-term price should be the same as the average long-term price with a ratio of one – this ratio is also known as the beta. When the average short-term price increases more than the average long-term price, the beta is more than one and the market can be viewed as on an uptrend. When the short-term price decreases more than the average long-term price, the beta is less than one and the market can be viewed as on a downtrend.

  • On an uptrend, as soon as the beta crosses above a certain price threshold level, our trading system shall generate a buy signal, and we can choose to enter into along position with a buy market order.
  • Likewise, on a downtrend, when the beta falls below a certain price thresholdlevel, a sell signal is generated and we can choose to enter into a short position with a sell market order.

     The moment that a position is opened, the same signals may be used to close out our position.

  • When a long position is opened, we close our position on a sell signal by entering an order to sell at the market.
  • Likewise, when a short position is opened, we close out our position on a buy signal by entering an order to buy at the market. 

     The mechanics mentioned are very similar to those of the mean-reversion trading system design. Bear in mind that this algorithm does not guarantee any profits, and is just a simplistic belief of the markets. You should have a different (and better) view than this.

https://www.geeksforgeeks.org/args-kwargs-python/

Special Symbols Used for passing arguments:

  • *args (Non-Keyword Arguments)
    def myFun(*argv):
        for arg in argv:
            print(arg)
     
     
    myFun('Hello', 'Welcome', 'to', 'GeeksforGeeks')

    def myFun(arg1, *argv):
        print("First argument :", arg1)
        for arg in argv:
            print("Next argument through *argv :", arg)
     
     
    myFun('Hello', 'Welcome', 'to', 'GeeksforGeeks')

     

  • **kwargs (Keyword Arguments)
    def myFun(**kwargs):
        for key, value in kwargs.items():
            print("%s == %s" % (key, value))
     
     
    # Driver code
    myFun(first='Geeks', mid='for', last='Geeks')


 

def myFun(arg1, arg2, arg3):
    print("arg1:", arg1)
    print("arg2:", arg2)
    print("arg3:", arg3)
 
 
# Now we can use *args or **kwargs to
# pass arguments to this function :
args = ("Geeks", "for", "Geeks")
myFun(*args)
 
kwargs = {"arg1": "Geeks", "arg2": "for", "arg3": "Geeks"}
myFun(**kwargs)

 

def myFun(*args, **kwargs):
    print("args: ", args)
    print("kwargs: ", kwargs)
 
 
# Now we can use both *args ,**kwargs
# to pass arguments to this function :
myFun('geeks', 'for', 'geeks', first="Geeks", mid="for", last="Geeks")

 

Writing the trend-following trader class

     Let's write a class for our trend-following trading system with a new class named TrendFollowingTreader , which simply extends the MeanReversionTrader class using the following Python code:

     In our constructor, we define three additional keyword arguments, long_mean_periods, buy_threshold, and sell_threshold ,saved as class variables. The long_mean_periods variable defines the number of resample intervals of our time series prices to take into account for calculating the long-term average price. Note that the existing mean_periods variable in the parent constructor is used for calculating the short-term average price. The buy_threshold and sell_threshold variables contain values that determine the boundaries of beta in generating a buy or sell signal. (The choice of buy_threshold and sell_threshold can be trained with a large amount of real-time data)

     Because only the decision-making logic needs to be modified from our parent MeanReversionTrader class, and everything else, including orders, placement, and streaming prices, remains the same, we simply override the generate_signals_and_think() method and implement our new trend-following signal generators using the following code: 

class TrendFollowingTrader( MeanReversionTrader ):
    def __init__( self, *args, long_mean_periods=10,
                  buy_threshold=1.0, sell_threshold=1.0, **kwargs
                ):
        super( TrendFollowingTrader, self ).__init__(*args, **kwargs)
        
        self.long_mean_periods = long_mean_periods
        self.buy_threshold = buy_threshold
        self.sell_threshold = sell_threshold
        
    def generate_signals_and_think( self ):
        df_resampled = self.df_prices.resample( self.resample_interval )\
                                     .ffill().dropna()
        resampled_len = len( df_resampled.index )
        
        if resampled_len < self.long_mean_periods:
            print( 'Insufficient_data size to calculate logic. Need',
                   self.mean_periods - resampled_len, 
                   'more.'
                 )
            return
        
        # tail(self.mean_periods) takes the most recent resampled intervals(resample_interval) and
        # calculates the average using the mean()
        mean_short = df_resampled.tail( self.mean_periods ).mean()[self.symbol]
        mean_long = df_resampled.tail( self.long_mean_periods ).mean()[self.symbol]
        beta = mean_short / mean_long
        
        # Signal flag calculation
        is_signal_buy = beta > self.buy_threshold
        is_signal_sell = beta < self.sell_threshold
        
        print( 'is_signal_buy:', is_signal_buy,
               'is_signal_sell:', is_signal_sell,
               'beta:', beta,
               'bid:', self.bid_price,
               'ask:', self.ask_price
             )
        self.think( is_signal_buy, is_signal_sell )

     As before, on every invocation of the generate_signals_and_think() method, we resample prices at fixed intervals, defined by resample_interval. The minimum intervals required for the calculation of signals is now defined by long_mean_periods instead of mean_periods . The mean_short variable refers to the short-term average resampled price, and the mean_long variable refers to the long-term average resampled price.

     The beta variable is the ratio of the short-term average price to the long-term average price.

  • When the beta rises above the buy_threshold value, a buy signal is generated and the is_signal_buy variable is True .
  • Likewise, when the beta falls below the sell_threshold value, a sell signal is generated and the is_signal_sell variable is True.

     The trading parameters are printed to the console for debugging purposes, and the call to the parent think() class method triggers the usual logic of buying and selling with market orders. 

Running the trend-following trading system

     Let's start our trend-following trading system by instantiating the TrendFollowingTrader class and running it using the following code:

# Replace these 2 values with your own!
ACCOUNT_ID = '101-001-23023947-001'
API_TOKEN = '795cc4bb80b7e2eb0c7c314ee196dbae-2871ba6122cb4c670a2439e07f998611'

broker = OandaBroker( ACCOUNT_ID, API_TOKEN )

trader = TrendFollowingTrader( broker,
                               resample_interval='60s',
                               symbol='EUR_USD',
                               units=1,
                               mean_periods=5,
                               long_mean_periods=10,
                               buy_threshold=1.000010,
                               sell_threshold=0.99990,
                             )
trader.run()

     The first parameter, broker , is the same object created for our broker in the previous section. Again, we are resampling our time series prices at one-minuteintervals('60s'), and we are interested in trading the EUR/USD currency pair, entering into a position of at most one unit at any given time. With a mean_periods value of 5, we are interested in taking the most recent 5 resampled intervals in calculating the average price of the past five minutes as our short-term average price. With a long_mean_period of 10, we are interested in taking the most recent 10 resampled intervals in calculating the average price of the past 10 minutes as our long-term average price.

     The ratio of theshort-term average price to the long-term average price is taken as the beta. When the beta rises above the value defined by buy_threshold , a buy signal is generated. When the beta falls below the value defined by sell_threshold , a sell signal is generated.

     With our trading parameters set up, the run() method is called to start the trading system. We should see an output on the console that is similar to the following:
...

...

     At the start of trading, we obtained the current market prices, staying in a flat position with neither profits nor losses. There is insufficient data available to make any trading decisions, and we will have to wait 10 minutes before we can see the calculated parameters take effect.https://www.oanda.com/demo-account/

 

     If your trading system depends on a longer period of past data and you do not wish to wait for all this data to be collected, consider bootstrapping your trading system with historical data

     Let the system run on its own for awhile, and it should be able to close out positions on its own. To stop trading, terminate the running process with Ctrl + Z, or something similar. Remember to manually close out any remaining trading positions once the program stops running. Take steps to change your trading parameters and decision logic to make your trading system a profitable one! 

     Note that the author is not responsible for any outcomes of your trading system! In a live trading environment, it takes more control parameters, order management, and position tracking to manage your risk effectively. 

     In the next section, we will discuss a risk management strategy that we can apply to our trading plans.

pa8_Contract for Difference CFD Trading_Oanda_git_log return_Momentum Strategy_Leverage_margin_tpqoa​​​​​​​: 

t5_Sophisticated Algorithmic Strategies(MeanReversion+APO+StdDev_TrendFollowing+APO)_StatArb统计套利_PnL​​​​​​​ 

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share​​​​​​​ : https://blog.csdn.net/Linli522362242/article/details/122159869

VaR(value-at-risk) for risk management 

     As soon as we open a position in the market, we are exposed to various types of risks, such as volatility risk and credit risk. To preserve our trading capital as much as possible, it is important to incorporate some form of risk management measures to our trading system.

     Perhaps the most common measure of risk used in the financial industry is the VaR technique. It is designed to simply answer the following question: What is the worst expected amount of loss, given a specific probability level, say 95%, over a certain period of time?
(https://blog.csdn.net/Linli522362242/article/details/124977553
VaR is quoted报价 as a currency amount and represents the maximum loss to be expected given both a certain time horizon and a confidence level以货币金额报价,表示在特定时间范围(30天, start='2018-06-01 00:00:00', stop='2018-06-30 00:00:00')和置信水平下 预期的最大损失

The beauty of VaR is that it can be applied to multiple levels, from position-specific micro-level to portfolio-based macro-level. For example, a VaR of $1 million with a 95% confidence level for a 1-day time horizon states that, on average, only 1 day out of 20 could you expect to lose more than $1 million due to market movements.1 天时间范围内 95% 的置信水平的 100 万美元的风险价值表明,平均而言,20 天中只有 1 天可以预期由于市场波动而损失超过 100 万美元。

     The following diagram illustrates a normally distributed portfolio returns with amean of 0%, where VaR is the loss corresponding to the 95th percentile of the distribution of portfolio returns:

     Suppose that we have $100 million under management at afund claiming to have the same risk as an S&P 500 index fund, with an expected return of 9% and a standard deviation of 20%. To calculate the daily VaR at the 5% risk level or 95% confidence level using the variance-covariance method, we will use the following formulas:

Here, P is the value of the portfolio, andN^{-1}(\alpha, u,\sigma )is the inverse normal probability
distribution
with a risk level of\alpha, a mean ofu, a and standard deviation of\sigma. The number
of trading days per year is assumed to be 252. It turns out that the daily VaR at the 5% level
is $2,036,606.50.

import scipy.stats as st
import numpy as np


# the 'inverse' normal cumulative distribution function
# https://blog.csdn.net/Linli522362242/article/details/103646927
# confidence (interval)= 0.95 ==> (1-confidence) = 0.05
# P(z<Z?) = 0.05 ==> Z = -0.020366064952458116
# ppf(q, loc=0, scale=1) or ppf(prob, mean, stddev)


norminv = st.distributions.norm.ppf

portfolio = 100000000.00

prob = 0.95 # or confidence = 0.95
alpha = 1-prob

mean = 0.09 # yearly expected return=0.09
stdev = 0.2 # a standard deviation of 20%
days_per_year = 252

u = mean/days_per_year # daily expected return
sigma = stdev/np.sqrt(days_per_year)

norminv = norminv(alpha, u, sigma)

portfolio - portfolio*(norminv+1)

 
cdf: https://blog.csdn.net/Linli522362242/article/details/125863306

     However, the use of VaR is not without its flaws缺陷. It does not take into account the probability of the loss for extreme events happening on the far ends of the tails on the normal distribution curve. The magnitude of the loss beyond a certain VaR level is difficult to estimate as well.

     The VaR that we investigated uses historical data and an assumed constant volatility level – such measures are not indicative of our future performance.

     Let's take a practical approach to calculate the daily VaR of stock prices; we will investigate the AAPL stock prices by downloading from a data source: 

import yfinance as yf

start_date='1998-01-01'
end_date = '2018-11-24'
aapl_df = yf.download( 'AAPL', start=start_date, end=end_date)

aapl_df

 

aapl_df.info()


     Our DataFrame contains 6 columns, with prices starting from the year 1998 to the present trading day. The column of interest is theadjusted closing prices. Suppose that we are interested in calculating the daily VaR of 2017; let's obtain this dataset using the following code:

The prices variable contains our AAPL dataset for 2017.

     Using the formulas discussed earlier, you can implement the calculate_daily_var() function using the following code: 

import datetime as dt
import pandas as pd

# Define the date range
start = dt.datetime(2017, 1, 1)
end = dt.datetime(2017, 12, 31)

# Cast indexes as DateTimeIndex objects
# aapl_df.index = pd.to_datetime(df.index)
closing_prices = aapl_df['Adj Close']
prices = closing_prices.loc[start:end]

from scipy.stats import norm

def calculate_daily_var( portfolio, prob, mean, 
                         stdev, days_per_year=252.
                       ):
    alpha = 1-prob # left tail
    
    u = mean/days_per_year
    sigma = stdev/np.sqrt(days_per_year)
    
    norminv = norm.ppf(alpha, u, sigma)
    
    return portfolio - portfolio*(norminv+1)

     Let's assume that we are holding $100 million of AAPL stock, and we are interested in finding the daily VaR at the 95% confidence level. We can define the VaR parameters using the following code:

     The mu and sigma variables represent thedaily mean percentage returns and the daily standard deviation of returns respectively. 

We can obtain the VaR (value-at-risk) by calling the calculate_daily_var() function, as follows:

import numpy as np

portfolio = 100000000.00
confidence = 0.95

# https://blog.csdn.net/Linli522362242/article/details/126269188
# pct_change() : prices-prices.shift(1)/prices.shift(1)
daily_returns = prices.pct_change().dropna()

mu = np.mean( daily_returns )
sigma = np.std( daily_returns )

VaR = calculate_daily_var( portfolio, confidence,
                           mu, sigma,
                           days_per_year=252., # trading days in on year
                         )
print('Value-at-Risk: %.2f' % VaR)

     Assuming 252 trading days per year, the daily VaR of 2017 for the stock AAPL with 95% confidence is $114,265.86.

Summary

     In this chapter, we were introduced to the evolution of trading from the pits to the electronic trading platform, and learned how algorithmic trading came about. We looked at some brokers offering API access to their trading service offering. To help us get started on our journey of developing an algorithmic trading system, we used the Oanda v20 library to implement a mean-reversion trading system. 

     In designing an event-driven broker interface class, we defined event handlersfor listening to orders, prices, and position updates. Child classes inheriting the Broker class simply extend this interface class with broker-specific functions, while still keeping the underlying trading functions compatible with our trading system. We successfully tested the connection with our broker by getting market prices, sending a market order, and receiving position updates.

     We discussed the design of a simple mean-reversion trading system that generates buy or sell signals based the movements of historical average prices, and opening and closing out our positions with market orders. Since this trading system uses only one source of trading logic, more work will be required to build a robust, reliable, and profitable trading system.

We also discussed the design of a trend-following trading system that generates buy or sell signals based on the movements of a short-term average price against a long-term average price. With a well-designed system, we saw how easy it was to make a modification to the existing trading logic by simply extending the mean-reversion parent class and overriding
the decision-making method.

 pa8_Contract for Difference CFD Trading_Oanda_git_log return_Momentum Strategy_Leverage_margin_tpqoa​​​​​​​: 

t5_Sophisticated Algorithmic Strategies(MeanReversion+APO+StdDev_TrendFollowing+APO)_StatArb统计套利_PnL​​​​​​​ 

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share​​​​​​​ : https://blog.csdn.net/Linli522362242/article/details/122159869

     One critical aspect of trading is to manage risk effectively. In the financial industry, VaR (value-at-risk)is the most common technique used to measure risk. Using Python, we took a practical approach to calculate the daily VaR of past datasets on AAPL.

     Once we have built a working algorithmic trading system, we can explore the other ways to measure the performance of our trading strategy. One of these areas is backtesting; we will discuss this topic in the next chapter.


版权声明:本文为Linli522362242原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。