- GuruFinance Insights
- Posts
- Automatic Crypto Portfolio Optimization Getting Easy
Automatic Crypto Portfolio Optimization Getting Easy
Here’s why workplace life insurance might not be enough
Unexpected events shouldn't leave your loved ones financially exposed. And while employer coverage is a nice perk, it usually only offers 2x your salary – far below the recommended 10-15x for real security (especially with dependents). Term life insurance can bridge the gap with customizable, affordable plans. Money’s Best Life Insurance list can help you find coverage starting at just $7/month, providing the peace of mind your family deserves.
🚀 Your Investing Journey Just Got Better: Premium Subscriptions Are Here! 🚀
It’s been 4 months since we launched our premium subscription plans at GuruFinance Insights, and the results have been phenomenal! Now, we’re making it even better for you to take your investing game to the next level. Whether you’re just starting out or you’re a seasoned trader, our updated plans are designed to give you the tools, insights, and support you need to succeed.
Here’s what you’ll get as a premium member:
Exclusive Trading Strategies: Unlock proven methods to maximize your returns.
In-Depth Research Analysis: Stay ahead with insights from the latest market trends.
Ad-Free Experience: Focus on what matters most—your investments.
Monthly AMA Sessions: Get your questions answered by top industry experts.
Coding Tutorials: Learn how to automate your trading strategies like a pro.
Masterclasses & One-on-One Consultations: Elevate your skills with personalized guidance.
Our three tailored plans—Starter Investor, Pro Trader, and Elite Investor—are designed to fit your unique needs and goals. Whether you’re looking for foundational tools or advanced strategies, we’ve got you covered.
Don’t wait any longer to transform your investment strategy. The last 4 months have shown just how powerful these tools can be—now it’s your turn to experience the difference.
Cryptocurrency investing is exhilarating, offering opportunities for significant gains — but it comes with unique risks. With the crypto market’s notorious volatility, managing your portfolio strategically is not just a luxury — it’s a necessity. Portfolio optimization helps you strike the right balance between risk and reward, enabling you to grow your investments while protecting them from sudden market swings.
In simple terms, portfolio optimization is the process of allocating your investments across different assets in a way that maximizes potential returns for a given level of risk. In traditional investing, this might mean balancing stocks and bonds. In crypto, it involves diversifying across coins, tokens, and even different sectors like DeFi, gaming, and Layer-1 blockchains.
As of today, December, 2024, we’re in the midst of a bull market — a time when optimism is high, and prices are surging. While it might feel like every investment is a winner right now, bull markets can be deceptive. They often amplify risks, with overconfidence leading to poorly balanced portfolios. This is why portfolio optimization is more crucial than ever: it ensures you capitalize on the bullish momentum while preparing for potential corrections.
In this blog, I’ll show you how to create your own crypto portfolio optimization using the Riskfolio library. You’ll learn how to dynamically adjust your portfolio for any timeframe you prefer, ensuring it evolves with the market. On top of that, I’ll guide you through deploying this solution with ease. As a bonus, we’ll explore how to use this powerful tool to optimize portfolios of strategies for algorithmic trading, helping you take your investing game to the next level. Let’s dive in!
🧠 AI’s Next Breakout Stock Is Here!
Every year, 600M people get strep, but testing is slow.
This company’s AI-powered Strep A test delivers fast, reliable results to your phone.
Its stock is soaring, hitting a $120M market cap in weeks.
What Should We Invest in Crypto?
Whether you’re drawn to crypto because you believe in its transformative technology or you’re chasing the thrill of quick gains from meme coins, the fundamental question remains: what should you invest in? The crypto market offers a wide array of assets, from established giants like Bitcoin and Ethereum to promising altcoins, DeFi tokens, NFTs, and even speculative meme coins. Each category comes with its own risk profile and potential rewards. Deciding where to allocate your funds requires clarity about your investment goals, risk tolerance, and market outlook. Are you looking for stability in blue-chip cryptos or willing to take on higher risk for potentially explosive returns with newer projects? Regardless of your motivation, a well-thought-out plan is essential to navigate this volatile market effectively. For the purpose of the blog I decided use these coins:
['BTC-USDT', 'ETH-USDT', 'LINK-USDT', 'XRP-USDT', 'DOGE-USDT', 'DOT-USDT', 'SOL-USDT']
Getting Started: Leveraging Jesse
If you’re diving into algo-trading for crypto and haven’t yet explored Jesse, it’s worth checking out. Jesse is a powerful research and trading library designed for algorithmic trading in crypto markets. While this blog focuses on building portfolio optimization tools, Jesse offers a comprehensive suite of features for backtesting, live trading, and more. If you’re unfamiliar with Jesse, I highly recommend visiting their official documentation to get a broader perspective on what it can do for your trading and research workflow.
For this blog, we’ll utilize Jesse to handle market data and execute backtests. If you’re already using Jesse, you’ll find it perfectly integrates with our optimization process, allowing you to bridge the gap between strategy research and portfolio management seamlessly.
Here’s a snapshot of the returns for six popular cryptocurrencies over the last 3 years. The plot below highlights the volatility and varying performance of these assets, with some showing significant gains and others experiencing sharp fluctuations. This diversity in returns demonstrates both the opportunities and risks of investing in crypto. Managing such a portfolio without a clear strategy can be challenging, which is why portfolio optimization is essential. By using these historical returns as our foundation, we’ll build an optimized portfolio that balances risk and reward effectively. Let’s dive into the process!
import pandas as pd
import jesse.helpers as jh
import matplotlib.pyplot as plt
from functools import partial
from jesse.research import get_candles
price_candles = partial(get_candles,
exchange='Binance Perpetual Futures',
timeframe='1D',
start_date_timestamp=jh.date_to_timestamp('2022-01-01'),
finish_date_timestamp=jh.date_to_timestamp('2024-12-01'),
)
assets = {
asset: price_candles(symbol=asset)[1]
for asset in ['BTC-USDT', 'ETH-USDT', 'LINK-USDT', 'XRP-USDT', 'DOGE-USDT', 'DOT-USDT', 'SOL-USDT']
}
CLOSE_INDEX = 2
TIMESTAMP_INDEX = 0
for asset, prices in assets.items():
plt.plot(pd.to_datetime(prices[:, TIMESTAMP_INDEX], unit='ms'),
prices[:, CLOSE_INDEX] / prices[0, CLOSE_INDEX], label=asset)
plt.ylabel('Return')
plt.xlabel('Time')
plt.title('Assets returns')
plt.legend(ncol=len(assets), loc="upper center", bbox_to_anchor=(0.5, -0.1))

Given the equity curves of these cryptocurrencies, we can optimize the portfolio to determine the ideal allocation for each asset at this moment. Using portfolio optimization, we can focus on different metrics, such as maximizing returns, minimizing risk, or achieving the best risk-adjusted returns (e.g., Sharpe ratio). We can also customize the optimization to fit specific goals, like maintaining a balanced exposure or limiting risk to certain coins. This flexibility allows us to tailor the portfolio to our unique preferences and market conditions. Here is a snippet of how to optimize the portfolio for maximum sharpe.
import riskfolio as rp
# riskfolio need the returns of the assets and not the equity curves as raw.
returns = pd.DataFrame.from_dict({
asset: prices[:, CLOSE_INDEX]
for asset, prices in assets.items()
}).pct_change()[1:] # first element is nan
port = rp.Portfolio(returns=returns)
method_mu='hist'
method_cov = "hist"
port.assets_stats(method_mu=method_mu, method_cov=method_cov)
hist = True
model = "Classic"
rm = "MV"
obj = "Sharpe"
w = port.optimization(model=model, rm=rm, obj=obj, hist=hist)
# Plotting the composition of the portfolio
ax = rp.plot_pie(w=w, title='Sharpe Mean Variance', others=0.0, cmap = "tab20",
height=4, width=8, ax=None)

given these weights we can decide these the amount of the portfolio for each coin.
* A very important note! this optimization is a very simple and optimize to the HISTORY, it says nothing about the future.
Jesse Backtest
Here is a simple strategy with backtest on this time range to compare the results.
import jesse.helpers as jh
from jesse import utils
from jesse.strategies import Strategy
class PortfolioOptimization(Strategy):
def should_long(self) -> bool:
return self.index == 0
def should_short(self) -> bool:
return False
def should_cancel_entry(self) -> bool:
return False
def go_long(self) -> None:
price = self.price
qty = utils.size_to_qty(self.balance * self.portfolio_weight, self.price, fee_rate=self.fee_rate)
self.buy = qty, price
@property
def portfolio_weight(self) -> float:
"""
# portfolio weight per symbol with default of 0.0
"""
return {
'DOGE-USDT': 0.387,
'XRP-USDT': 0.355,
'BTC-USDT': 0.223,
'SOL-USDT': 0.035,
}.get(self.symbol, 0.0)
Configuration:

Here are the results of the backtest and benchmark to the other crypto currencies. Obviously we wont beat DOGE and XRP but we maximize the sharpe ratio by getting a better portfolio allocation and minimize the risk.


For comparison, the Sharpe ratio of the optimized portfolio is 2.4, while individual assets SOL, XRP, and BTC have Sharpe ratios of 2.4, 2, and 2.2, respectively. This demonstrates that by diversifying and optimizing the allocation, we achieve a better risk-adjusted return (Sharpe) compared to investing all-in one single asset.
Top Investors Are Buying This “Unlisted” Stock
When the team that grew Zillow to a $16B valuation starts a new company, investors notice. No wonder top firms like SoftBank invested in Pacaso.
Taking the industry by storm all over again, Pacaso’s platform offers co-ownership of premier properties – completely revamping a $1.3T market.
And by handing keys to 1,500+ happy homeowners, Pacaso has made $100M+ in gross profits.
Now, with aggressive global expansion underway, their current share price won’t last long.
Dynamic Allocation
What we just did is fundamentally flawed because it introduces look-ahead bias. By using future data to optimize our portfolio, we create a scenario that isn’t achievable in real-time investing. In reality, we wouldn’t know the future returns when making allocation decisions, so this approach misrepresents the effectiveness of the strategy and could lead to unrealistic expectations.
Let’s assume we want to allocate our portfolio on the very first day, without any knowledge of future performance. Additionally, we plan to reallocate the assets every 3 days based the last 12 days. Here’s a snippet that demonstrates exactly how to implement this approach:
import pandas as pd
import riskfolio as rp
from jesse import utils
from jesse.strategies import Strategy
PORTFOLIO_UPDATE_INTERVAL = 3 # 3 candles
PORTFOLIO_UPDATE_INTERVAL_IN_MS = (
PORTFOLIO_UPDATE_INTERVAL * 24 * 60 * 60 * 1000
) # 3 days in ms for timeframe 1D
HISTORY_UPDATE_INTERVAL = 12
class PortfolioOptimization(Strategy):
def __init__(self):
super().__init__()
self.shared_vars["portfolio-allocation"] = None
self.shared_vars["portfolio-allocation-update-time"] = -1
def before(self):
if not self.should_update_portfolio():
return
self.update_portfolio_allocation()
def should_long(self) -> bool:
return (
self.time > self.shared_vars["portfolio-allocation-update-time"]
and self.portfolio_weight > 0
)
def go_long(self) -> None:
price = self.price
qty = utils.size_to_qty(
self.balance * self.portfolio_weight, self.price, fee_rate=self.fee_rate
)
self.buy = qty, price
def update_position(self) -> None:
# if we should create a new allocation
if self.time == self.shared_vars["portfolio-allocation-update-time"]:
self.liquidate()
@property
def portfolio_weight(self) -> float:
"""
# portfolio weight per symbol with default of 0.0
"""
if self.shared_vars["portfolio-allocation"] is None:
return 0.0
return self.shared_vars["portfolio-allocation"].get(self.symbol, 0.0)
def should_update_portfolio(self):
return (
self.shared_vars["portfolio-allocation"] is None
or self.time - self.shared_vars["portfolio-allocation-update-time"]
>= PORTFOLIO_UPDATE_INTERVAL_IN_MS
) # every 3 days
def update_portfolio_allocation(self):
if self.time == self.shared_vars["portfolio-allocation-update-time"]:
# another trading route already optimized the portfolio
return
self.shared_vars["portfolio-allocation-update-time"] = self.time
assets_returns = {
route.symbol: self.get_candles(self.exchange, route.symbol, self.timeframe)[
-HISTORY_UPDATE_INTERVAL - 1 :, 2
]
for route in self.routes
}
returns = pd.DataFrame(assets_returns).pct_change().dropna()
returns["USDT"] = 0
port = rp.Portfolio(returns=returns)
method_mu = "hist"
method_cov = "hist"
try:
port.assets_stats(method_mu=method_mu, method_cov=method_cov)
except:
return
hist = True
model = "Classic"
rm = "MV"
obj = "Sharpe"
w = port.optimization(model=model, rm=rm, obj=obj, hist=hist)
if w is not None:
self.shared_vars["portfolio-allocation"] = w["weights"].to_dict()
self.shared_vars["portfolio-allocation"] = {
asset: weight if weight > 5e-3 else 0
for asset, weight in self.shared_vars["portfolio-allocation"].items()
}
if self.shared_vars["portfolio-allocation"] is None:
self.shared_vars["portfolio-allocation"] = {
symbol: 1 / len(assets_returns) for symbol in assets_returns.keys()
}
Lets break it down:
At the beginning just imports and a const value that every 3 days we should reallocate the stack.
import pandas as pd
import riskfolio as rp
from jesse import utils
from jesse.strategies import Strategy
PORTFOLIO_UPDATE_INTERVAL = 3 # 3 candles
PORTFOLIO_UPDATE_INTERVAL_IN_MS = (
PORTFOLIO_UPDATE_INTERVAL * 24 * 60 * 60 * 1000
) # 3 days in ms for timeframe 1D
HISTORY_UPDATE_INTERVAL = 12
2. Next I define the strategy and initialize the parameters in the constructor. shared_vars
is a special variable of Strategy
that you can share the same instance of variable between the running strategies.
def __init__(self):
super().__init__()
self.shared_vars["portfolio-allocation"]: dict[str, float] | None = None
self.shared_vars["portfolio-allocation-update-time"]: int = -1
portfolio-allocation
is a dictionary that will map assets to its share in the portfolio.portfolio-allocation-update-time
set when was the last update. will be useful to update only once for the first asset that see his chance to update the allocation.
3. Then every new candle jesse start with the before
callback. There we start by checking if we should reallocate the portfolio. should_update_portfolio
check if its the first candle we start our backtest / live run or 3 days were passed.
def before(self):
if not self.should_update_portfolio():
return
self.update_portfolio_allocation()
def should_update_portfolio(self):
return (
self.shared_vars["portfolio-allocation"] is None
or self.time - self.shared_vars["portfolio-allocation-update-time"]
>= PORTFOLIO_UPDATE_INTERVAL_IN_MS
) # every 3 days
def update_portfolio_allocation(self):
if self.time == self.shared_vars["portfolio-allocation-update-time"]:
# another trading route already optimized the portfolio
return
self.shared_vars["portfolio-allocation-update-time"] = self.time
assets_returns = {
route.symbol: self.get_candles(self.exchange, route.symbol, self.timeframe)[
-HISTORY_UPDATE_INTERVAL - 1 :, 2
]
for route in self.routes
}
returns = pd.DataFrame(assets_returns).pct_change().dropna()
port = rp.Portfolio(returns=returns)
method_mu = "hist"
method_cov = "hist"
try:
port.assets_stats(method_mu=method_mu, method_cov=method_cov)
except:
return
hist = True
model = "Classic"
rm = "MV"
obj = "Sharpe"
w = port.optimization(model=model, rm=rm, obj=obj, hist=hist)
if w is not None:
self.shared_vars["portfolio-allocation"] = w["weights"].to_dict()
self.shared_vars["portfolio-allocation"] = {
asset: weight if weight > 5e-3 else 0
for asset, weight in self.shared_vars["portfolio-allocation"].items()
}
if self.shared_vars["portfolio-allocation"] is None:
self.shared_vars["portfolio-allocation"] = {
symbol: 1 / len(assets_returns) for symbol in assets_returns.keys()
}
update_portfolio_allocation
start by checking if it already updated. from another trading route. then we optimize the allocation given the new equity curves of the last 12 days.
Next we iterate the routes and get its candles equity curves.
assets_returns = {
route.symbol: self.get_candles(self.exchange, route.symbol, self.timeframe)[
-PORTFOLIO_UPDATE_INTERVAL - 1 :, 2
]
for route in self.routes
}
returns = pd.DataFrame(assets_returns).pct_change().dropna()
We save the result as a dictionary in self.shared_vars[“portfolio-allocation”]
that map the allocation by the symbol name.
In case of an error or no allocation exist we leave the allocation as it was.
4. In case we are not in position, the time is after the last update and there is an allocation to the asset we are currently in.
def should_long(self) -> bool:
return (
self.time > self.shared_vars["portfolio-allocation-update-time"]
and self.portfolio_weight > 0
)
5. Go long
def go_long(self) -> None:
price = self.price
qty = utils.size_to_qty(
self.balance * self.portfolio_weight, self.price, fee_rate=self.fee_rate
)
self.buy = qty, price
@property
def portfolio_weight(self) -> float:
"""
# portfolio weight per symbol with default of 0.0
"""
if self.shared_vars["portfolio-allocation"] is None:
return 0.0
return self.shared_vars["portfolio-allocation"].get(self.symbol, 0.0)
6. After the reallocation, we are in position so update_position
is getting called after before.
If we see there is a new allocation, liquidate the position, the next should_long
will let us enter the new allocation.
def update_position(self) -> None:
# if we should create a new allocation
if self.time == self.shared_vars["portfolio-allocation-update-time"]:
self.liquidate()
And here is the result with equity curve of :

Dynamic allocation every 3 days
With sharpe of 2.03 and drawdown of 35%, for comparison if we equally allocate our portfolio at the beginning this would be our equity curve with sharpe of 1.99 and drawdown of 39%

Equally allocate 1/7 of our capital for each asset
So we manage to improve our Sharpe (and our returns). But why do we want higher sharpe ratio? I wont elaborate on that subject but it gives us some sort of certainty on how much leverage we can have on our assets.
What Next?
There are several things that can help us improve our results even further.
Fees — For minimum fees instead of sell all of our assets and reallocate it, just reallocate the differences.
Constraints — Riskfolio support constraints on assets or group of assets such as I want at least 20% at bitcoin all the time or maximum 10% on all the meme coins together, Its a powerfull tool, check it out!
Better parameters — I used the Riskfolio to optimize the sharpe but there are other scores that you might find it more suitable to your persona.
Also models and methods. There are several hyperparameters for riskfolio, you should checkout each one of themTimeframe — In this example every 3 days we look at the reutrns of the last 12 days, you see that also here there are several hyperparameters?For example maybe every day look at last 72 hours will be better.
Allocate strategies — If you use jesse before then you probably have your own strategy, you can use the same methods to decide the allocation to different strategies. Good strategy is not just about good entries/exit and bet size. You need to be smarter how many eggs to put on every strategy!
To Sum Up
Riskfolio is another powerfull tool to your toolset, this one along won’t give you fast money, but it will make you more comfortable with your allocation decisions. Study this tool use it wisely and Im sure you will become a better algo-trader.