An agent in 100 lines of Lisp

The world of algorithmic trading and quantitative finance often conjures images of complex mathematical models and high-frequency trading systems. While these exist, the core concepts can be illustrated with surprisingly simple code. This article demonstrates how to build a basic financial agent in under 100 lines of Common Lisp. We’ll cover market simulation, defining trading rules, and a rudimentary backtesting mechanism. This isn’t intended to be production-ready code, but rather a learning exercise demonstrating the power and elegance of Lisp for financial modeling.
Why Lisp for Finance?
Before diving into the code, let's briefly consider why Lisp, and specifically Common Lisp, is a good choice for financial applications.
- Symbolic Computation: Lisp excels at manipulating symbols, making it ideal for representing financial instruments, rules, and market data.
- Flexibility & Extensibility: Lisp is dynamically typed and highly extensible. You can easily modify and add features without recompilation. Macros allow for metaprogramming – writing code that writes code.
- Functional Paradigm: The functional nature of Lisp encourages writing clear, concise, and testable code. This is especially important in finance, where accuracy is paramount.
- Rapid Prototyping: Lisp's interactive environment and concise syntax allow for rapid prototyping of trading strategies.
While Python has become extremely popular in finance, Lisp offers a powerful alternative, particularly for research and experimentation. If you’re looking for a book to help you get started with Common Lisp, consider https://example.com/ – Practical Common Lisp.
Setting the Stage: A Simple Market Simulation
Our agent needs a market to operate in. We'll create a very simplistic market simulation that generates random price movements.
```lisp
(defun generate-price (initial-price) (+ initial-price (random-float (- 1 1))))
This function takes an initial price and adds a random value between -1 and 1. This simulates a basic form of price fluctuation. We’ll need a function to simulate a series of prices over time.
```lisp
(defun simulate-market (initial-price num-steps) (loop for i from 0 below num-steps collect (generate-price (car (last (loop for j from 0 below i collect (generate-price initial-price)))))))
This simulates the market by iteratively generating prices, using the previous price as input to the generate-price function, introducing a degree of correlation. It’s a simplified random walk. More realistic models would incorporate volatility, trends, and other factors.
Defining the Trading Agent
Now comes the core of our agent. We'll define a simple trading rule: buy when the price drops below a certain threshold and sell when it rises above another.
```lisp
(defparameter buy-threshold 100) (defparameter sell-threshold 110) (defparameter initial-cash 1000)
(defun trade (price cash position)
(cond ((< price buy-threshold) (if (= position 0) (list price (+ cash (- 100)) 1) ; Buy 1 unit at the price (list price cash position))) ((> price sell-threshold) (if (> position 0) (list price (+ cash (* position price)) 0) ; Sell all units (list price cash position))) (t (list price cash position))))
This trade function takes the current price, current cash balance, and current position (number of units held) as input. It then checks if the price triggers a buy or sell signal. If a buy signal is triggered and the agent doesn't already hold any units, it buys one unit, deducting the cost from the cash balance. Similarly, if a sell signal is triggered and the agent holds units, it sells them, adding the proceeds to the cash balance. If no action is taken, it returns the current state.
Backtesting the Agent
To evaluate our agent, we’ll perform a simple backtest. This involves running the agent through historical (or simulated) market data and tracking its performance.
```lisp
(defun backtest (initial-price num-steps) (let ((market-data (simulate-market initial-price num-steps)) (cash initial-cash) (position 0)) (loop for price in market-data do (let ((result (trade price cash position))) (setf cash (first result)) (setf position (third result)))) (list cash position)))
The backtest function simulates the market, initializes the agent’s cash and position, and then iterates through the market data. For each price, it calls the trade function and updates the cash and position based on the returned values. Finally, it returns the final cash and position.
Putting it All Together: A Complete Example
Here's a complete, runnable example, combining all the functions. This is within our 100-line target (excluding comments).
```lisp
(defun generate-price (initial-price) (+ initial-price (random-float (- 1 1))))
(defun simulate-market (initial-price num-steps)
(loop for i from 0 below num-steps collect (generate-price (car (last (loop for j from 0 below i collect (generate-price initial-price)))))))
(defparameter buy-threshold 100)
(defparameter sell-threshold 110) (defparameter initial-cash 1000)
(defun trade (price cash position)
(cond ((< price buy-threshold) (if (= position 0) (list price (+ cash (- 100)) 1) ; Buy 1 unit at the price (list price cash position))) ((> price sell-threshold) (if (> position 0) (list price (+ cash (* position price)) 0) ; Sell all units (list price cash position))) (t (list price cash position))))
(defun backtest (initial-price num-steps)
(let ((market-data (simulate-market initial-price num-steps)) (cash initial-cash) (position 0)) (loop for price in market-data do (let ((result (trade price cash position))) (setf cash (first result)) (setf position (third result)))) (list cash position)))
(let ((result (backtest 100 100)))
(format t "Final Cash: A%" (first result))
(format t "Final Position: A%" (second result)))
This code can be run in a Common Lisp REPL (Read-Eval-Print Loop) like SBCL or CCL. You’ll need to have Common Lisp installed on your system. A good beginner-friendly distribution is Steel Bank Common Lisp (SBCL).
Improving the Agent & Further Exploration
This agent is extremely simplistic. Here are some ways to improve it:
- More Sophisticated Trading Rules: Implement moving averages, RSI, or other technical indicators.
- Risk Management: Add stop-loss orders and position sizing strategies.
- Transaction Costs: Incorporate brokerage fees and slippage.
- Realistic Market Data: Use real historical market data instead of a random walk. You can download data from sources like Yahoo Finance or Quandl.
- Optimization: Use optimization techniques (e.g., genetic algorithms) to find optimal trading parameters.
- Portfolio Management: Expand the agent to manage a portfolio of multiple assets.
Table of Key Parameters
| Parameter | Description | Default Value |
|------------------|--------------------------------------|---------------|
| *buy-threshold* | Price below which to buy | 100 |
| *sell-threshold* | Price above which to sell | 110 |
| *initial-cash* | Initial cash balance of the agent | 1000 |
Conclusion
This article demonstrates how to build a basic financial agent using Common Lisp in a concise and elegant manner. While this is a simplified example, it illustrates the potential of Lisp for financial modeling and algorithmic trading. Lisp's symbolic manipulation capabilities, functional paradigm, and extensibility make it a powerful tool for quantitative finance professionals and researchers. Consider using a good integrated development environment (IDE) like SLIME with Emacs to streamline your Lisp development. For further learning and resources, check out the Common Lisp HyperSpec online documentation https://example.com/.
Disclaimer: This article is for informational purposes only and does not constitute financial advice. The trading agent described is a simplified example and should not be used for live trading without thorough testing and understanding. Algorithmic trading involves significant risk, and you could lose money. Affiliate links are included for products that may be helpful for learning and development; we receive a small commission if you purchase through these links.