Contango
- 7 Devlogs
- 76 Total hours
Backtest, optimize, and analyze trading strategies over tens of thousands of parameters at once.
Backtest, optimize, and analyze trading strategies over tens of thousands of parameters at once.
Research has been rough. I haven’t found any real edge with a
calmar ratio above 1.0, nor a sharpe ratio that is statistically meaningful to suggest a somewhat profitable strategy.
This system was absolutely effective in what it did; however, I’ve concluded that using brute-force to generate strategies does not yield any
genuine edge. The system was powerful, but honestly hard to use; hence, I came to the decision of deleting it as it did not have much use other than
brute-forcing trading strategies.
Rule-based strategies, again, allow for strategies to be created with minimal and highly declarative code. The API has improved significantly
with less unnecessary generics and more docstrings. It will be the intended system for backtesting strategies rather than using static strategies. They will both coexist, however; the static strategies will be intended to be used for live trading if implemented in the future.
The graph system was relatively ad-hoc, hard to use, and hardcoded; therefore, I have restructured it to take a more composable approach to
allow for the user to create & add graphs. The default graphs have not disappeared; however, they can be overridden. New graphs can be subclassed and the user can register & create custom graphs if desirable.
I’m beginning to wrap up the project to get it to a publishable state.
…But that’s okay.
I built a system capable of searching an enormous strategy space, and the system worked. My original goal was to have computers literally generate trading strategies and hypothesize by themselves through testing all possible indicator combinations. However, simple strategies based on widely known indicators aren’t producing the profitability I was initially looking for.
However, this isn’t necessarily bad news - it just means that the system should be used to help hypothesizing rather than automating it entirely. The same system can be used heavily for optimization of a concept. For example, it can completely automate the process of testing if ATR scaling or an RSI confirmation yields better results. Further, I can still find optimal parameters through the grid search for a given strategy.
A grid search inherently will find “amazing” results - however, that doesn’t necessarily mean that it would actually perform well in the market. The parallel coordinate graphs, heatmap graphs, and manual walk-forward tests have made it genuinely easy to pick out overfit results, so this accepts part of my hypothesis in the earlier devlogs.
I finished most of the work I wanted to do. I stress tested the suite, created unit tests, and created the hyper-parameterization system in its entirety. Now is finally the fun part - research. I’m genuinely curious if I’m able to find some profitable strategies with my system, so my future devlogs will likely be related to the research I’m going to conduct.
That’s all. Have a great day!
Apparently this isn’t easy to design! Who could have thought? Not me!
Jokes aside, this has been a miserable experience. I’ve created two different designs so far. The first one failed outright - far in, I realized that having mutable objects in a nested grid search breaks all of the results, and it wasn’t easy to integrate Callable types with what I had already created, so I scrapped it.
Another massive hurdle was trying to find out how to parameterize the rules that indicators must follow (i.e. close crosses above middle bollinger band crosses). The idea here is to have a set of rules, and have each and every one of them parameterized amongst each other to find the best rules in the subset.
My current design uses callables to generate fresh instances of both the parameters and indicators used in the hyperparameter grid search.
However, another issue has become known to me - how do you derive unique, dynamically created names for each individual parameterized result? My thought is to derive the name of the indicators from the caller (which I have been doing), but on top of that, force all rule-based modules in my codebase to implement an ABC & override repr. This has not been formally implemented yet.
I found a lot of issues and limitations with them. However, after working for hours coding them, I believe I’ve reached a point where I’m happy with how they’re looking.
To summarize, every strategy is composed of rules, where a rule has a condition & action. If the condition is true, the action (intent) is released. In the case of this suite, intents express to buy, sell, create stoploss orders, etc. You can see the syntax in the attached screenshot.
Unit tests are completely finished! The suite has 290 passing unit tests as of now.
I’m going to give parameterization another try. Instead of creating another layer on rule-based strategies, I’m going to instead pass indicators (or “streams” as I call them) into a base strategy along with their parameters in an attempt to perform a Cartisan hyperparameter grid search for both the indicators and their parameters. That was a mouthful to say; however, I’ll demonstrate what I mean in the next devlog (hopefully).
As always, I like to document what I want to accomplish before shipping:
That’s it. Happy coding!
I completed the rule-based strategy engine, and it seems to be yielding correct results with integration tests. It allows for both Cartesian Parameterization & indicator parameterization, mentioned in the previous devlog.
I’ve noticed that as I make more changes, my unit tests have been lacking. The majority of the time for this devlog has been spent creating unit tests for my code. This has proved to be very time consuming and tedious (it always is), and has taken more than five hours at this point. However, I’m only halfway done, so this might take a while longer…
I always like to make a list for what I want to do before shipping. Here’s where I’m at now:
That’s all. Have a great rest of your day!
Massive changes have been made relating to strategy creation & the capability of the framework.
An entire Bollinger Band Mean Reversion strategy now looks like this:
indicators = {"bb": BollingerBands(period=period, k=num_std)}
rules: Sequence[Rule[TradingContext, Intent]] = [
Rule(
Predicate(lambda ctx: ctx.event.close < ctx.indicators["bb"].value.lower),
Emit(EnterLongIntent(symbol=symbol))
),
Rule(
Predicate(lambda ctx: ctx.event.close > ctx.indicators["bb"].value.upper),
Emit(ExitIntent(symbol=symbol)),
)
]
super().__init__(
indicators=indicators,
rules=rules,
position_sizer=AllocationPositionSizer(size)
)
With this structure, an entire trading strategy can be expressed with a few unequivocal rules.
Further, because indicators and their parameters are major components, both can now be parameterized during optimization. That means grid searches can iterate over not only different parameter values, but entirely different indicator combinations.
In a favorable environment, this means that with enough computing power, you can automate the creation and development of strategies. However, in practice, real markets make this significantly less straightforward due to overfitting; therefore, human analysis is still essential since larger search spaces dramatically increase the risk of these biases. Nonetheless, this gives a major advantage to manually creating code for each strategy individually.
There are some things I want to do before shipping. I’ll list them here:
Anyway, enough technical talk for this devlog - happy coding!
I have finally released Contango on PyPI!
Contango is a full trading engine that helps you build trading strategies. It does all of the heavy lifting - the results, metrics, and the entire trading engine behind the hood.
I’ve never created a package on PyPI, so doing this was a massive learning experience - from uploading to TestPyPI to iterating multiple times before I got my pyproject.toml right.
I sent one of the graphs generated after testing my project demo out using the package.
See the package on PyPI here: https://pypi.org/project/contango/
Have a great rest of your day!