Patterns in Time Series
Meme coins often display sentiment-driven extreme volatility and speculative behavior. Yet, recurring weekly price patterns—particularly early-week lows and weekend highs—have been observed. This paper investigates whether such patterns can be systematically detected, predicted, and exploited algorithmically. For Pattern prediction and recognition we propose a layered framework combining standard weekly reference profiles, forward-looking classifiers, and entropy-aware real-time resemblance checks to evaluate meme coin price behavior. This approach seeks to balance predictive modeling with live confirmation, making algorithmic trading more adaptive to volatile markets.
A version of this paper is also published on LinkedIn.

Price Patterns and Algorithmic Trading
With these concepts in mind, we analyze meme coins to identify whether such temporal structures exist in practice. Empirical evidence points to a recurring weekly rhythm. Using historical price data our research uncovered for the Central European Time (CET) zone a recurring pattern with a weekly periodicity of early-week lows followed by weekend highs for meme coins in particular.
This predictable rhythm suggests opportunities for short-term trading strategies. Having also demonstrated that it’s relatively easy to build and automate a personal crypto trading signal bot, we decided to add one and one together. Our research focuses on detecting such weekly structures and determining whether they can be leveraged in a systematic, automated pipeline. Thus we build a script to efficiently processes a watch-list of assets to identify those fit to use in a trading signal generation bot.
Toolkit and Performance
The architecture combines early-week pattern recognition with intraweek signal generation in a layered decision system. To systematically exploit this, we developed a toolkit for prediction and real-time monitoring with three main components:
- Reference weekly profiles – asset-specific average patterns for similarity matching.
- Forward-looking classifiers – trained models predicting whether next week will conform to the reference pattern.
- Decision mechanisms – real-time checks of early-week factual resemblance with the reference profile.
We use these three combined tools to select candidate assets we then feed to a signal generating bot that searches high frequency live data per asset for a significant combination of indicators to confirm the buy/sell pattern inherent in the shape of the reference pattern for this asset.
Despite this complete set of tools, overall performance during the summer was disappointing. To a large extent, this was due to the prevailing downward market trend. An overall negative market movement, that in particular hits meme coins. These mostly lost between one third and half of their value in the last couple month. During this period our ‘forward looking volatility-tolerant’ classifier consistently, week-after-week showed (rightly!) no confidence that assets on the watch-list of meme coins would follow the reference pattern. In the initial implementation the classifier acted as a strict gatekeeper, effectively blocking all candidates!
Limitations of the Gatekeeper Approach
We observed that high volatility disrupts the classifier’s predictions. Standard similarity metrics assume equal time spacing, consistent amplitude and phase alignment while in practice patterns often were compressed, stretched, or phase-shifted. So for meme coins we experimented with volatility-tolerant measures, fuzzy logic approaches that might accommodate for distortions with tolerance bands and allowing for greater leeway in weeks with elevated volatility. Still if even a ‘volatility-tolerant’ classifier predicted that assets in the coming week would not really follow their reference profile, does this mean no opportunities did in fact occur during this week? Mostly indeed they didn’t, but we did witness actual opportunities arise!
Tiered Selection Workflow
So even if we missed them on Sunday we still did have a chance to find likely candidates when the new week had started. Especially in the period before the expected low hour in the reference profile.
A probable resemblance before takeoff is one thing but a factual resemblance once things have really started is what we’re really looking for. If we want to do justice to this reality check we no longer can maintain the gatekeeper role of the classifier. If we wanted to be able to select all possible candidates for algorithmic trading we had to adjust the processing workflow accordingly. We needed to move from gatekeeping to tiered selection. We use the classifier first to select probable candidates without excluding those not selected, instead offering them a second change based on early week actual similarity with the reference profile. Appendix A shows how watch-list processing is adjusted.
Entropy as Refinement
We adjusted the classifier to better be able to cope with volatility. But volatility ≠ unpredictability. It is not volatility in itself that hinders predictability. The real culprit is entropy. Entropy in information theory quantifies the level of uncertainty or unpredictability in a system. In finance, it has been applied to measure the randomness or unpredictability of asset prices. High entropy indicates markets are unpredictable, while low entropy suggests periods of more stable behavior. Studies have shown that cryptocurrency markets tend to have higher entropy than traditional equity markets, indicating greater uncertainty and volatility.
We adjusted the processing workflow and have given greater responsibility to the early week similarity check. We also bolstered it. By incorporating entropy into our early-week resemblance check, we distinguish noisy volatility from truly chaotic regimes, improving selection by applying an entropy penalty or bonus. See Appendix B Evaluate Early Week for details.
Volatility vs Entropy
The distinction between volatility and entropy lies fundamentally in what they measure and how they relate to market behavior.
Volatility is a measure of size (of price differences). Volatility refers to the magnitude of price changes over a given period. It measures how spread out or dispersed asset prices are around a mean or expected value. In simple terms, volatility tells us how much prices are changing, regardless of the direction. A market with high volatility will experience large price swings. A market with low volatility will have more stable or smaller price changes. We compute volatility with statistical measures such as standard deviation or variance of asset returns.
Entropy, in the context of information theory, measures uncertainty or unpredictability. It quantifies the lack of information or the amount of surprise in the observed data. In financial markets entropy reflects the degree of predictability of asset prices. A market with high entropy means that future price movements are highly uncertain. Low entropy indicates that price movements follow a more predictable patterns. Entropy doesn’t care about the size of price changes; it cares about how random or structured the price movements are.
Even if two markets have the same volatility, one may have high entropy (unpredictable) and the other low entropy (more predictable). If a stock’s price follows a clear trend for a long period, the entropy will be low. The market is predictable. If the stock price fluctuates wildly without any clear trend, the entropy will be high, as predicting the next move is difficult.
Empirical Results
Meme coins are highly volatile and often exhibit random, unpredictable price movements. Sometimes driven by social sentiment, news events, or speculative trading. On a micro level (e.g., 15-minute candlesticks), short-term noise often dominates price fluctuations. However, they can still contain patterns related to momentary volatility bursts or shifts in trading momentum.
Shannon entropy quantifies the uncertainty or randomness in a system. A higher entropy value in the time series implies that the price movements are more unpredictable (i.e., the market is in a state of high uncertainty or disorder). Low entropy suggests more predictable or stable behavior. In the case of meme coins, high entropy could indicate a high likelihood of volatile behavior in the near future, as prices are fluctuating unpredictably. Conversely, low entropy could suggest that the price is following a more consistent or trending pattern.
In the early-week resemblance check we compute the Shannon entropy of the candidate asset as a measure of its predictability. Calculating entropy on a micro level—such as a dataset of 500 data points of 15-minute candlesticks for a meme coin—provides useful insights. We interpret the scores as follows:
- high H_norm ≥ ~0.88, the market regime for this asset is chaotic;
- mid H_norm ~0.80–0.85, this still is fairly noisy;
- low H_norm ≤ ~0.75, this suggests more structure, likely pattern-conformity.
Appendix C shows the method used to compute the Shannon entropy-score.
Discussion & Implications
What have we learned over the past months? Testing during the summer we were confronted with a downward trend bias. Meme coins lost 30–50% of their value. The classifier correctly showed little confidence in conformity. One lesson we learned: performance is regime-dependent; prolonged bearish trends limit exploitable opportunities.
Adjusting our workflow we were able to benefit from a tiered approach. We identify opportunistic entries that under the gatekeeper model would have been blocked.
Given attention to the role of entropy in unpredictability helped filter chaotic weeks and highlight structured opportunities. The layered system—reference profiles, classifier, real-time resemblance, and entropy-balancing—yielded a more adaptive and resilient framework, though absolute opportunities remained limited under bearish market conditions.
For meme coin traders our findings highlight the importance of paying attention to the special role entropy plays in the predictability of market situations. Traders gain a more nuanced way to exploit meme coin cycles. Entropy offers a generalizable lens for short-term predictability. Traders can use entropy-enhanced pattern recognition to adapt trading in highly speculative assets. Distinguishing volatility from unpredictability is critical. Although especially true for meme coins to some extent this conclusion is generalizable to markets in general.
Our contributions this far focused on empirical observation of weekly lows/highs in meme coins, a layered system combining prediction and real-time checks, integration of entropy into pattern recognition and empirical evaluation under adverse (bearish) conditions.
Future research should explore integrating sentiment data, refining entropy-based thresholds, and testing across broader asset classes. Ultimately, combining predictive classifiers with entropy-aware real-time checks may generalize into a framework for short-term pattern exploitation in volatile markets. Also using reinforcement learning to dynamically adapt thresholds may boost results.
Conclusion & Outlook
With this research we demonstrate that meme coin weekly price structures can be modeled through a layered, entropy-aware framework that balances predictive classifiers with real-time confirmation.
Key takeaway: Volatility does not equal unpredictability. Entropy provides the missing dimension. For traders, this means that monitoring entropy alongside volatility may improve decision-making in short-term speculative markets.
Future work should explore scaling this framework beyond meme coins—into micro-cap equities, FX, and other volatile asset classes—while refining entropy-based thresholds and integrating broader data sources.
Appendix A Processing the Watch-List
def process_watch_list(self):
counter = 0
pred_hit = 0
fact_hit = 0
both_hit = 0
rows = []
BOT_list = []
asset_diagnostics = []
if not self.WATCH_LIST:
self.lu.do_log("* * * 🚫 No assets in WATCH_LIST.", self.asset)
return both_hit, BOT_list
for self.asset in self.WATCH_LIST:
counter += 1
print(f"\n\n* * * {counter}.{self.asset}")
self.lu.log_separator()
self.lu.do_log("* * * week-check started", self.asset)
# to keep state of asset between runs
rec = self.state_dict.setdefault(self.asset, {
"A_pass": False, "A_proba": None,
"B_state": "PENDING", # PENDING|PASS|FAIL
"weighted_score": None,
"post_cutoff": False,
})
# step 1: --- Complete Asset History and build Reference weekly pattern for Asset OR load a presaved Reference Pattern from disk---
if not self.PREP_RUN:
succ, mess, self.reference_profile, self.low_hour, self.high_hour, self.low_value, self.high_value = self.reference_manager.load_reference_profile(self.asset)
else:
succ, mess, self.reference_profile, self.low_hour, self.high_hour, self.low_value, self.high_value = self.reference_manager.create_save_reference_profile(self.asset)
self.lu.do_log(mess, self.asset)
if not succ:
continue
self.lu.do_log(f'* * * 📉 Low Hour and Value: {self.lu.hour_of_week_to_str(self.low_hour)} → {self.low_value:.3f}, 📈 High Hour and Value: {self.lu.hour_of_week_to_str(self.high_hour)} → {self.high_value:.3f}', self.asset)
if self.PREP_RUN:
succ, mess = self.reference_manager.train_save_forward_looking_vt_pattern_classifier(tolerance_pct=2.0, strategy = "meme", mae_thresh=90, fuzzy_thresh=75)
self.lu.do_log(mess, self.asset)
if not succ:
continue
# step 2: --- Check Entry condition A: Probability of New Week Similarity with Reference Pattern ---
mess, a_probable_pattern, proba = self.reference_manager.check_weekly_pattern_resemblance(proba_threshold = self.proba_threshold)
self.lu.do_log(mess, self.asset)
self.proba = proba
if a_probable_pattern:
pred_hit += 1
# step 3: --- Check entry condition B Current Data Early Week Factual Resemblance ---
self.lu.do_log(f"* * * 📥 Downloading most recent {self.FREQ} data from Binance", self.asset)
success, self.current_df = self.download_data(self.asset, self.FREQ)
if not success:
self.lu.do_log("* * * ❌ Failed to obtain current data", self.asset)
continue
# check factual resemblance and log diagnostics
msg, b_factual_resemblance, scores, weighted_score = self.evaluate_early_week_entry(a_probable_pattern, proba, band_width=self.band_width, score_threshold = self.score_threshold, asset_diagnostics = asset_diagnostics)
self.lu.do_log(msg, self.asset)
self.lu.do_log(scores, self.asset)
if b_factual_resemblance:
fact_hit += 1
# step 4: --- Evaluate Combined Entry Condition Probability Confirmed ---
is_pre_cutoff = ("N/A" in scores) # reference low hour not passed yet / final decision still pending!
if a_probable_pattern and b_factual_resemblance:
both_hit += 1
BOT_list.append(self.asset)
# save conformity probability and factual resemblance score with calculated combined entry score in dict/json file to use in SignalBot
combined_score = (float(proba) + (2.0 * weighted_score)) / 3.0 # weight factual resemblance score double (prefer factual resemblance over predicted probability)
self.conformity_result[self.asset.upper()] = {
"conforms": bool(proba > self.proba_threshold),
"confidence": round(float(proba), 4),
"weighted_score": round(float(weighted_score), 4),
"combined_score": round(float(combined_score), 4)
}
self.lu.do_log(f"* * * ✅ Both pattern prediction and early-week match passed. Asset {self.asset} added to Bot-list and prediction saved!", self.asset)
elif a_probable_pattern and is_pre_cutoff:
self.lu.do_log("* * * 🟡 A-pattern prediction-passed; B pending (still pre-cutoff hour for reference low).", self.asset)
elif a_probable_pattern and not is_pre_cutoff:
self.lu.do_log("* * * 🔴 A-pattern prediction-passed; B failed post-cutoff hour for reference low.", self.asset)
# opportunistic B-only: new candidate
if (not a_probable_pattern) and b_factual_resemblance and (not is_pre_cutoff) and self.allow_opportunistic_b_only:
if weighted_score >= self.opportunistic_score_threshold:
BOT_list.append(self.asset)
self.lu.do_log("* * * ✅ Opportunistic B-only entry accepted as new candidate!", self.asset)
# update asset state
rec["A_pass"] = bool(a_probable_pattern)
rec["A_proba"] = float(proba)
rec["B_state"] = "PASS" if b_factual_resemblance else ("PENDING" if is_pre_cutoff else "FAIL")
rec["weighted_score"] = float(weighted_score)
rec["post_cutoff"] = not is_pre_cutoff
# summary
rows.append({
"asset": self.asset,
"predicted": bool(a_probable_pattern),
"proba": round(float(proba or 0), 3),
"factual": bool(b_factual_resemblance),
"weighted_score": round(float(weighted_score or 0), 3)
})
# step 5: --- Visual diagnostics ---
if self.visuals:
succ, mess = self.reference_manager.plot_current_week_vs_average()
self.lu.do_log(mess, self.asset)
self.reference_manager.plot_live_vs_reference(self.current_df, cutoff_hour=self.low_hour, band_width=self.band_width)
# end of WATCH_LIST
# save entry_state assets updated this run
self.entry_store.save(self.entry_state_path, self.state_dict)
self.lu.log_separator()
self.lu.write_log(self.log,f"Number of Assets processed: {counter}, pattern similarity predicted: {pred_hit}, factual resemblance found: {fact_hit}, both: {both_hit}\n")
pred = {r["asset"] for r in rows if r["predicted"]}
fact = {r["asset"] for r in rows if r["factual"]}
both = sorted(pred & fact)
print(f"\nPredicted: {sorted(pred)}")
print(f"Factual: {sorted(fact)}")
print(f"Overlap: {both} (count={len(both)})")
if asset_diagnostics:
mess = self.reference_manager.save_diag_excel_calc_watch_window(asset_diagnostics)
self.lu.write_log(self.log, mess)
print(f"\n{mess}")
msi = self.monitor.get_meme_season_index()
mess = f"\n* * * {msi['MSI Signal']}: BTC Change: {msi['BTC Δ%']}% and Average Meme Coin Change: {msi['Avg Meme Δ%']}%, resulting MSI: {msi['MSI']}%"
self.lu.write_log(self.log, mess)
print(f"{mess}")
return both_hit, BOT_list
PythonAppendix B Evaluate Early Week
def evaluate_early_week_entry(self, a_probable_pattern, proba, band_width = 2, score_threshold = 85, asset_diagnostics = None):
# live_df: DataFrame with recent 15-min price data (must include current Monday)
# reference_profile_hourly: Series with hour_of_week index and normalized price pattern
# cutoff_hour: How many hours into Monday to allow for evaluation until the weekly low from the standard weekly price profile for the asset
# Note: prefer the classifier’s frozen reference for B as well: B’s baseline identical to A’s by default
model_path = Path(self.reference_manager.classifiers_path) / f"{self.mrkt}_fuzzy_pattern_forward_classifier.pkl"
reference_profile_hourly = self.reference_profile
cutoff_hour = self.low_hour
if model_path.exists():
try:
loaded = joblib.load(model_path)
if isinstance(loaded, dict) and 'ref_profile' in loaded:
ref_series = pd.Series(loaded['ref_profile'], dtype=float).sort_index()
ref_series.index = ref_series.index.astype(int)
reference_profile_hourly = ref_series # use same baseline as A
if 'expected_low_hour' in loaded and loaded['expected_low_hour'] is not None:
cutoff_hour = int(loaded['expected_low_hour'])
except Exception as e:
pass # fall back to preset self.reference_profile loaded from json
# Step 1: Interpolate hourly pattern to 15-minute resolution
reference_15min = reference_profile_hourly.reindex(reference_profile_hourly.index.repeat(4)).interpolate(method='linear').reset_index(drop=True)
reference_15min.index.name = 'quarter_hour_of_week'
# Step 2: Filter live data for the most recent Monday 00:00 to cutoff (the refence low hour + band width)
live_df = self.current_df.copy()
this_monday = (pd.Timestamp.now(tz=live_df.index.tz).normalize() - pd.to_timedelta(pd.Timestamp.now(tz=live_df.index.tz).weekday(), unit='D'))
live_df = live_df[live_df.index >= this_monday]
live_df['hour_of_week'] = live_df.index.dayofweek * 24 + live_df.index.hour
end_hour = cutoff_hour + band_width
live_df = live_df[live_df['hour_of_week'] < end_hour]
mess = f"* * * 🕒 Found {len(live_df)} 15m data points on Monday before hour {end_hour}, e.g. cutoff_hour+band_width ({cutoff_hour}+{band_width}). From {live_df.index.min()} to {live_df.index.max()}"
if len(live_df) < 4:
mess = f"* * * ⚠️ Not enough data points for evaluation yet."
self.lu.do_log(mess, self.asset)
return mess, False, {}
# Step 3: Normalize price to 00:00 = 100
base_price = live_df.iloc[0]['close']
live_df['normalized_price'] = (live_df['close'] / base_price) * 100
# Step 4: Align length with interpolated reference pattern
live_pattern = live_df['normalized_price'].reset_index(drop=True)
reference_segment = reference_15min.iloc[:len(live_pattern)]
if len(reference_segment) != len(live_pattern):
mess = f"* * * ⚠️ Mismatch in pattern lengths."
self.lu.do_log(mess, self.asset)
return mess, False, {}
# Step 5: Compute similarity
mae_score = 100 - np.mean(np.abs(live_pattern - reference_segment))
cosine_score = cosine_similarity(live_pattern.values.reshape(1, -1), reference_segment.values.reshape(1, -1))[0][0] * 100
# Step 6: Estimate price slope and shape
price_slope = np.polyfit(np.arange(len(live_pattern)), live_pattern, deg=1)[0]
actual_low_index = live_pattern.idxmin()
actual_low_time = actual_low_index / 4 # since 4 points per hour
max_live_hour = live_df['hour_of_week'].max()
likely_low = None
time_diff_from_ref = None
if max_live_hour >= cutoff_hour:
diff = abs(actual_low_time - cutoff_hour)
# smooth timing score (never zero)
timing_score = max(0.25, np.exp(-(diff**2) / (2 * 3.0**2)))
ptime_diff = f"{diff:.2f} hr"
# ✅ set likely_low here
likely_low = (diff <= self.band_width)
time_diff_from_ref = diff
else:
timing_score = 0.5
ptime_diff = "N/A"
# can't judge yet
likely_low = None
# Step 7: Entropy on recent log returns (last ~25 hours = 100x 15m) ---
ENT_BINS = 30
ENT_WIN = 100 # 100 x 15m ≈ 25 hours
# Use the most recent returns up to "now" (not only Monday slice)
df_all = self.current_df.copy()
df_all['log_ret'] = np.log(df_all['close']).diff()
# Take last ENT_WIN returns (drop NaN from diff)
recent_rets = df_all['log_ret'].dropna().tail(ENT_WIN).values
if recent_rets.size < max(20, ENT_WIN // 4):
# not enough history to form a stable entropy estimate -> neutral
H, Hmax, Hnorm = 0.0, np.log(ENT_BINS), 0.5
else:
H, Hmax, Hnorm = self._shannon_entropy(recent_rets, bins=ENT_BINS)
# Map to a score where LOW entropy strengthens pattern-conformity (1 - H_norm)
entropy_score = 1.0 - Hnorm # ∈ [0,1], lower is "more orderly"
# Step 8: Apply decision criteria
# If timing not yet measurable use a pre-cutoff threshold (lower the threshold)
if ptime_diff == "N/A":
score_threshold = score_threshold - self.pre_ref_lowering
# scale slope
SLOPE_SCALE = 0.20 # tune if needed
slope_score = (np.clip(price_slope, -SLOPE_SCALE, SLOPE_SCALE) + SLOPE_SCALE) / (2 * SLOPE_SCALE)
if self.use_entropy_bonus_or_score == 'score':
weighted_score = (
0.30 * np.clip(mae_score / 100, 0, 1) +
0.30 * np.clip(cosine_score / 100, 0, 1) +
0.15 * timing_score +
0.10 * slope_score +
0.15 * np.clip(entropy_score, 0, 1)
)
else:
weighted_score = (
0.35 * np.clip(mae_score / 100, 0, 1) +
0.35 * np.clip(cosine_score / 100, 0, 1) +
0.20 * timing_score +
0.10 * slope_score
)
# Adjust weighted score for entropy
if Hnorm > 0.87: # entropy_score ≤ 0.12 regime is chaotic → penalize pattern-based entries
factor = self.entropy_penalty
elif Hnorm < 0.76: # entropy_score ≥ 0.25 bonus to pattern-conformity
factor = self.entropy_bonus
else: # still fairly noisy but acceptable for meme
factor = 1
weighted_score = weighted_score * factor
decision = round(weighted_score, 2) >= round(score_threshold, 2)
# score_details: dict with similarity scores and timing info, decision: True if pattern matches and entry is likely valid
mess = '* * * Not Enough Factual Resemblence!'
if decision:
mess = '* * * Factual Resemblance established!'
scores = (
f"* * * mae: {round(mae_score, 2)}, "
f"cosine: {round(cosine_score, 2)}, "
f"price_slope: {round(price_slope, 3)}, "
f"likely_low: {likely_low}, time_diff_from_ref: {ptime_diff}, "
f"entropy: {round(Hnorm, 2)}, score: {round(entropy_score, 2)}, "
f"weighted: {round(weighted_score, 2)} - threshold: {score_threshold}"
)
# f"entropy(H): {round(H, 4)} / Hmax: {round(Hmax, 4)} "
# Save asset diagnostics
result_dict = {
"a_probable_pattern": a_probable_pattern,
"proba": proba,
"decision": decision,
"weighted_score": weighted_score,
"mae_score": mae_score,
"cosine_score": cosine_score,
"price_slope": price_slope,
"actual_low_time": actual_low_time,
"time_diff_from_ref": time_diff_from_ref,
"timing_score": timing_score,
"entropy": H,
"entropy_norm": Hnorm,
"entropy_score": entropy_score,
}
self.reference_manager.add_asset_diagnostics(result_dict=result_dict, asset_diagnostics=asset_diagnostics)
return mess, decision, scores, weighted_score
PythonAppendix C Shannon Entropy
# compute Shannon Entropy of the assets time series
def _shannon_entropy(self, arr, bins=30, eps=1e-12):
"""
Returns (H, H_max, H_norm) for a 1D array `arr`.
- H: Shannon entropy of binned distribution (natural log)
- H_max: log(bins)
- H_norm: H / H_max in [0,1]
"""
arr = np.asarray(arr)
arr = arr[np.isfinite(arr)]
if arr.size == 0:
return 0.0, np.log(bins), 0.0
hist, _ = np.histogram(arr, bins=bins, density=False)
p = hist.astype(float)
s = p.sum()
if s <= eps:
return 0.0, np.log(bins), 0.0
p /= (s + eps)
p = p[p > 0]
H = -np.sum(p * np.log(p + eps))
H_max = np.log(bins)
return float(H), float(H_max), float(H / H_max)
Python