Abstract
This paper extends previous research into weekly price patterns in the cryptocurrency market by exploring whether predictable intra-week patterns can be used for algorithmic trading. Having established robust standard weekly price patterns, we can confidently use these as reference profiles for similarity matching. In addition, we developed a forward-looking classifier capable of forecasting whether the upcoming week is likely to follow the expected pattern. Moreover, we possess a mechanism to verify the factual early-week resemblance to this reference using real-time data. By integrating these components with essential utilities—for data acquisition, time-zone handling, JSON-based configuration loading, and structured logging—we implemented a modular Python class that serves as the foundation of a fully automated selection pipeline. This script efficiently processes a watchlist of assets and identifies suitable candidates for a trading signal generation bot. This architecture combines early-week pattern recognition with intraweek signal generation, creating a layered decision system.
Note: This research paper has also been published on LinkedIn.
Lookback & look ahead
The first paper in this series began with the intuitive hypothesis that cryptocurrency prices—particularly those of meme coins—tend to dip over the weekend. However, the analysis revealed a different yet consistent pattern. Meme coin prices typically decline at the start of the week and rise toward a peak at the end of the week. While this weekly cycle also appears in traditional cryptocurrencies, it is less pronounced. A closer examination of the most recent complete year, 2024, shows this pattern emerging even more distinctly. It is important to note, however, that this represents the typical or ‘normal’ pattern. Under extreme market conditions, weekly price movements tend to mirror broader trends, whether bullish or bearish.
The first part of this study concluded with a compelling question. If a consistent weekly price pattern can be identified, how effectively can it be leveraged for algorithmic trading? To explore this, we first clarify the underlying terminology. Then we examine whether—and how—such a strategy can be implemented using a practical, do-it-yourself framework.
Weekly Patterns and Algorithmic Trading
The term algorithmic trading often carries an intimidating connotation, largely because it is commonly associated with high-speed, high-frequency strategies employed by major financial institutions. However, at its core, algorithmic trading simply refers to the application of rule-based logic—grounded in empirically testable knowledge—through a mathematical model executed by a computer with network access. As Investopedia notes, “To get started with algorithmic trading, you must have computer access, network access, financial market knowledge, and coding capabilities.”
Importantly, neither the underlying mathematical model nor the required domain knowledge needs to be overly complex. In a related study, we demonstrated how a relatively straightforward Python script can generate entry and exit signals using real-time data from the Binance API. This script leverages common momentum indicators from technical analysis (TA) libraries—such as moving averages—to evaluate market conditions and make trading decisions based on predefined rules. In doing so, it fulfills the core requirements of algorithmic trading in a practical and accessible manner.
The Nature of the Beast
Having clarified the theoretical foundation of rule-based trading signals, we now turn to the practical question: can our newly acquired knowledge be reliably applied in real trading scenarios? For the Central European Time (CET) zone, we observed a consistent pattern in which prices tend to dip early in the week before rising toward a peak near the end of the week. To evaluate the practical relevance of this observation, we must determine whether the established ‘standard pattern’ merely reflects a statistical average or whether it actually recurs with high regularity under typical market conditions.
Usability Check: Similarity matching
To assess how frequently the standard pattern occurs in practice, we require suitable metrics for comparing price patterns. Several well-established similarity measures can serve this purpose:
- MAE (Mean Absolute Error): Captures the average absolute difference between two signals. It is effective for quantifying overall shape mismatch and serves as a strict deviation filter.
- MSE (Mean Squared Error): Measures the average of squared differences, placing more weight on large deviations. However, it is less interpretable due to the squared units.
- Cosine Similarity: Calculates the angle between two vectors in a high-dimensional space. It focuses on trend similarity rather than magnitude, making it suitable for comparing normalized patterns.
- DTW (Dynamic Time Warping): Aligns two sequences by allowing non-linear time shifts to minimize total distance. It is capable of recognizing patterns with temporal delays (e.g., shifted peaks) but can be sensitive to noise and is less intuitive than MAE.
Each of these metrics has distinct advantages and limitations for pattern comparison tasks. Using the method described earlier—analyze_weekly_price_patterns—we recreated the standard weekly pattern for each asset and compared it with historical weekly patterns. This allowed us to evaluate the effectiveness of different similarity measures in identifying recurring price behaviors.
Calculating & Plotting Similarity
def plot_weekly_similarity_scores(self, metric='mae'):
self.do_message(f"🔍 Computing weekly similarity using metric: {metric.upper()}")
profile_df = self.analyze_weekly_price_patterns(price_col='close')
if profile_df.empty:
self.do_message("⚠️ No data available for similarity scoring.")
return
# Step 1: Reference profile: mean normalized price per hour
reference_profile = profile_df.groupby('hour_of_week')['normalized_price'].mean().sort_index()
reference_array = reference_profile.to_numpy().reshape(1, -1)
weekly_scores = []
for week_start, group in profile_df.groupby('week_start'):
week_series = group.groupby('hour_of_week')['normalized_price'].mean().sort_index()
# Ensure complete alignment to reference
week_series = week_series.reindex(reference_profile.index)
if week_series.isnull().any():
continue # skip incomplete weeks
week_array = week_series.to_numpy()
try:
if metric == 'mae': # Mean Absolute Error
score = 100 - np.mean(np.abs(week_array - reference_array.flatten()))
elif metric == 'mse': # Mean Squared Error
score = 100 - np.mean((week_array - reference_array.flatten())**2)
elif metric == 'cosine': # Cosine Similarity
sim = cosine_similarity(week_array.reshape(1, -1), reference_array)[0][0]
score = sim * 100
elif metric == 'dtw': # Dynamic Time Warping
dist = dtw.distance(week_array, reference_array.flatten())
score = max(0, 100 - dist)
else:
self.do_message(f"❌ Unknown metric: {metric}")
return
except Exception as e:
self.do_message(f"⚠️ Skipping week {week_start} due to error: {e}")
continue
weekly_scores.append({'week_start': week_start, 'similarity_score': score})
# Step 3: Plot results
score_df = pd.DataFrame(weekly_scores)
if score_df.empty:
self.do_message("⚠️ No complete weeks available for similarity scoring.")
return
fig = px.line(score_df, x='week_start', y='similarity_score', title=f'{self.mrkt} — Weekly Pattern Similarity ({metric.upper()})', labels={'week_start': 'Week Start', 'similarity_score': 'Similarity Score (%)'})
fig.update_layout(yaxis_range=[0, 100], template='plotly_white')
fig.add_hline(y=90, line_dash='dot', line_color='green', annotation_text='90% Threshold')
fig.show()
self.do_message(f"✅ Finished plotting similarity scores ({metric.upper()}).")
PythonPattern Detection: MAE and Cosine Similarity Combined
In this context, the complementary strengths of MAE—which emphasizes strict shape comparison—and Cosine Similarity—which highlights trend alignment—make them particularly well-suited for identifying recurring weekly price patterns. When combined in a single plot, these two metrics provide a dual perspective: MAE quantifies overall trend deviation, while Cosine Similarity captures shape alignment. This integrated approach allows for an immediate and intuitive assessment of pattern conformity. To facilitate this, we adapted the earlier analysis method to compute both metrics in parallel and upgraded the visualization to enhance interpretability and visual appeal.
Volatility Fading with Age
Analyzing weekly similarity patterns across the historical price data of various assets—particularly over the past 1.5 years—yielded two key findings. First, we observed consistent and recognizable patterns, validating the premise that weekly profiles can support pattern-based trading signals. Second, we confirmed a strong inverse correlation between an asset’s age and its volatility, not only in absolute price movement but also in the stability of its weekly pattern. As anticipated, younger meme coins exhibited the greatest deviations. However, even among highly volatile assets such as WIF, PEPE, and BONK, adherence to the established weekly pattern remained remarkably high. Given the centrality of consistent pattern recognition in this approach, we applied a stringent similarity threshold of 90% to ensure only highly reliable signals were retained. The following visualization illustrates the performance for BONK—a 30-month-old asset—across the most recent 18 months.

For the archetypical meme coin DOGE, a 137-month-old asset, the same major deviations of course are there but the overall picture is more ‘mature’, keeping above the threshold.

In extremis this is true for the godfather of crypto BTC, a 197-month-old asset, almost every weekly volatility has seemingly disappeared, it simply sticks to its, much less pronounced pattern.

Age & Volatility
This strong correlation between asset age and weekly pattern similarity is both statistically and market-dynamically intuitive. From a statistical perspective, a limited price history tends to yield a noisier average, as individual weeks often diverge from a still-developing baseline. Marketwise, younger assets typically exhibit lower liquidity and heightened volatility, even in the presence of an overarching macro trend. As assets mature, however, a more stable rhythm tends to emerge. Established cryptocurrencies such as BTC, ETH, and even DOGE benefit from increasing institutional participation and the emergence of recurring market behaviors—such as miner sell-offs, weekly portfolio rebalancing, and trader psychology. Collectively, these factors foster the development of statistically ‘learnable’ and reproducible weekly patterns, enhancing both MAE and Cosine Similarity over time.
Predicting Probability
High historical similarity does not guarantee that the upcoming week will follow the expected pattern. If our goal is to translate this knowledge into actionable trading signals, we require a degree of confidence that the forthcoming week will indeed resemble the established standard. While no prediction can be absolute, past behavior remains the strongest available indicator of future behavior. This raises a critical question: Can we use recent pattern similarity to forecast whether the upcoming week will conform to the default weekly pattern?
One simple approach is to define a rolling similarity signal based on the past three to five complete weeks, such as: rolling_similarity = score_df[‘similarity_score’].rolling(window=3).mean()
This would involve thresholding both MAE and Cosine Similarity scores above 90%. However, this static method may lack flexibility. A more refined alternative is to train a lightweight classifier to predict weekly pattern conformity using a combination of features—such as recent similarity scores, weekly volatility, and the deviation of key points (e.g., lows) from the reference pattern. This approach enhances the simple threshold logic by offering explainability, deterministic reproducibility, and improved precision. By combining quantified historical similarity (MAE and Cosine) with temporal trends and a probabilistic confidence layer, we enable a more robust decision-making framework—balancing rule-based logic with machine learning adaptability.
Feature Engineering
You enable predictive modeling of weekly pattern conformity by constructing a feature set for each historical week. These features capture both similarity metrics and market characteristics that may influence pattern adherence.

To each weekly feature vector we assign a binary label: 1 if both MAE > 90% and Cosine Similarity > 99%, and 0 otherwise. This enables the model to learn what patterns and conditions typically precede weeks that conform to the standard profile.
Model Choice
We begin with logistic regression as a transparent and interpretable baseline. However, Random Forest and LightGBM are better suited for capturing nonlinear interactions between features. In particular, Random Forest offers access to feature-importances, which quantifies the relative contribution of each input feature to the model’s decisions. You can use it for feature pruning, interpretability, and explanation generation.
To operationalize this workflow, we implement a method train_forward_looking_pattern_classifier(), which:
- Constructs the training dataset using the asset’s pre-analyzed weekly history,
- Trains the classification model,
- Prints a summary of results and feature importance,
- Saves the trained classifier for future inference.
Training the Classifier
def train_forward_looking_pattern_classifier(self, profile_df, threshold=90):
profile_df = profile_df.copy()
# ⛔️ Filter out current (possibly incomplete) week
now = pd.Timestamp.now(tz=profile_df.index.tz)
this_monday = (now - pd.to_timedelta(now.weekday(), unit='D')).normalize()
profile_df = profile_df[profile_df['week_start'] < this_monday]
# Compute expected weekly pattern
reference_profile = profile_df.groupby('hour_of_week')['normalized_price'].mean().sort_index()
reference_array = reference_profile.to_numpy().reshape(1, -1)
expected_low_hour = reference_profile.idxmin()
rows = []
for week_start, group in profile_df.groupby('week_start'):
week_series = group.groupby('hour_of_week')['normalized_price'].mean().sort_index()
week_series = week_series.reindex(reference_profile.index)
if week_series.isnull().any():
continue
week_array = week_series.to_numpy()
mae_score = 100 - np.mean(np.abs(week_array - reference_array.flatten()))
cosine_score = cosine_similarity(week_array.reshape(1, -1), reference_array)[0][0] * 100
low_hour = week_series.idxmin()
high_hour = week_series.idxmax()
volatility = np.std(week_array)
range_percent = np.max(week_array) - np.min(week_array)
regime = self.get_week_market_regime(group) # 'bullish', 'bearish', or 'neutral'
rows.append({
'week_start': week_start,
'mae_score': mae_score,
'cosine_score': cosine_score,
'low_hour': low_hour,
'high_hour': high_hour,
'volatility': volatility,
'range_percent': range_percent,
'diff_from_pattern_low': abs(low_hour - expected_low_hour),
'regime': regime
})
df_feat = pd.DataFrame(rows).sort_values('week_start')
for col in ['mae_score', 'cosine_score']:
df_feat[f'rolling_{col}_mean'] = df_feat[col].rolling(3).mean()
df_feat[f'rolling_{col}_std'] = df_feat[col].rolling(3).std()
df_feat['future_label'] = (df_feat['mae_score'].shift(-1) > threshold).astype(int)
df_feat = df_feat.dropna()
df_feat = pd.get_dummies(df_feat, columns=['regime'])
regime_cols = [col for col in df_feat.columns if col.startswith('regime_')]
features = [
'rolling_mae_score_mean', 'rolling_mae_score_std',
'rolling_cosine_score_mean', 'rolling_cosine_score_std',
'volatility', 'range_percent',
'diff_from_pattern_low'
] + regime_cols
X = df_feat[features]
y = df_feat['future_label']
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X, y)
importances = model.feature_importances_
importance_df = pd.DataFrame({'feature': X.columns, 'importance': importances}).sort_values('importance', ascending=True)
fig = px.bar(importance_df, x='importance', y='feature', orientation='h',
title='🔮 Forward-Looking Feature Importances (Random Forest)',
text=importance_df['importance'].round(3))
fig.update_layout(template='plotly_white', xaxis_range=[0, importance_df['importance'].max() * 1.1])
fig.update_traces(textposition='outside')
fig.show()
self.print_classifier_report(model, df_feat)
print("\n📈 Feature correlations with future_label:")
for col in features:
corr = df_feat[col].corr(df_feat['future_label'])
print(f"{col:<35} → {corr:+.3f}")
joblib.dump(
{'model': model, 'features': features},
f"{self.mrkt}_pattern_similarity_forward_classifier.pkl"
)
print("✅ Forward-looking classifier saved.")
Python
Fuzzy Logic
So far, the forward-looking pattern similarity classifier performs as intended. In principle, this prepares us for the next phase: evaluating whether early-week price behavior aligns with the standard weekly pattern. However, during testing, we observed a critical limitation—moderate to high volatility often disrupts the classifier’s predictions, even when the underlying shape of the pattern remains intact.
This discrepancy arises because standard similarity metrics—such as Cosine Similarity and Mean Absolute Error (MAE)—are sensitive to volatility distortions and assume equal time spacing, consistent amplitude, and phase alignment. In practice, however, patterns may become compressed, stretched, or phase-shifted, particularly under volatile market conditions. The example below illustrates this challenge. Although the overall topology of the expected pattern is preserved, the live pattern is compressed into a shorter time window, with sharper swings and a delayed weekly low. Despite the structural similarity—and the presence of a potential 10% intra-week return—the classifier fails to recognize the match. This scenario underscores the need for volatility-tolerant similarity measures or fuzzy logic approaches that can accommodate such distortions, allowing us to better detect functional rather than literal pattern matches.
Toward a Volatility-Tolerant Classifier
To address the limitations of traditional similarity metrics under volatile conditions, we introduce a pattern-aware but volatility-tolerant (VT) classifier. While the two classifiers—train_forward_looking_pattern_classifier and train_forward_looking_vt_pattern_classifier—are structurally similar in their architecture and training pipeline, they differ fundamentally in how they define and assess similarity between a given week’s price behavior and the reference pattern.
Key Differences
The key conceptual differences are as follows:
- Similarity Definition: The original classifier relies on Mean Absolute Error (MAE) and Cosine Similarity as strict measures of deviation from the reference pattern. The VT classifier extends this by introducing a fuzzy similarity score, which measures the proportion of hourly price points that fall within a volatility-scaled tolerance band around the reference pattern.
- Error Tolerance: In the original approach, tolerance is fixed, enforcing rigid error margins regardless of market conditions. In contrast, the VT classifier employs an adaptive tolerance, allowing greater leeway for weeks with elevated volatility. This makes it more robust to structural similarity despite temporary amplifications or compressions in amplitude.
- Rolling Metrics: Both classifiers use rolling means and standard deviations for MAE and Cosine scores to capture recent trends. The VT classifier augments this by also calculating rolling statistics for the fuzzy similarity score, enriching the feature set with volatility-normalized pattern adherence.
By integrating the fuzzy similarity score into the feature set, the VT classifier gains the flexibility to recognize structurally similar weekly patterns even when amplitude varies significantly. We achieve this by scaling the tolerance dynamically based on the volatility of each week:

Practically, this means the classifier first computes the average historical volatility across all prior weeks. Then, for each training week, it determines the current week’s volatility, uses this to scale the tolerance band, and calculates the fuzzy similarity score accordingly. This approach enables greater resilience to volatility distortions while maintaining sensitivity to meaningful structural patterns.
Defining the Target: MAE vs. Fuzzy Similarity
The most critical distinction between the original and the volatility-tolerant classifiers lies in the target definition—i.e., how we assign the future_label that the model is trained to predict. In the original classifier, the label is based solely on the MAE score: df_feat[‘future_label’] = (df_feat[‘mae_score’].shift(-1) > threshold).astype(int)
A label of 1 indicates that the next week’s price pattern has a MAE similarity above the defined threshold (e.g., 90%), suggesting strong adherence to the reference pattern. This is intuitive and directly reflects absolute deviation, making it effective for clean, low-noise assets. However, it is also highly sensitive to volatility and amplitude distortions, which can unjustly penalize structurally correct patterns—especially for younger meme coins, as demonstrated in the similarity plots.
An alternative is to define the label using the fuzzy similarity score instead: df_feat[‘future_label’] = (df_feat[‘fuzzy_similarity_score’].shift(-1) > threshold).astype(int)
This approach encourages the model to identify weeks that approximate the overall shape of the reference pattern, even if the amplitude varies significantly. The result is a classifier that is more tolerant to volatility, reducing the number of false negatives (weeks incorrectly discarded due to noise). While this may slightly decrease precision (i.e., more false positives), it typically improves recall, increasing the model’s ability to identify all relevant weeks—an attractive trade-off in the context of meme coins.
mae OR fuzzy
A stricter alternative would be to require both MAE and fuzzy scores to exceed the threshold (mae AND fuzzy). While this results in higher precision, it also reduces recall—missing structurally valid but noisy weeks, which is counterproductive for our objective. A more flexible and pragmatic approach is to define the label as (mae OR fuzzy): future_label = (mae_score > threshold) OR (fuzzy_score > threshold). This inclusive OR logic allows a week to qualify if either its absolute shape (MAE) or volatility-adjusted structure (fuzzy) matches the reference. It broadens the classifier’s scope without sacrificing the core pattern integrity—particularly useful when dealing with volatile, emerging meme coins.
Thus, we adopt a differentiated strategy aligned with the nature of the asset class:
- For meme coins, the label is based on mae OR fuzzy logic to maximize early-week sensitivity.
- For trad coins, the label is based on mae only to minimize false positives and ensure precision.
This tailored approach improves both the adaptability and robustness of the system, aligning predictive behavior with the volatility and maturity characteristics of the asset under consideration.
Volatility Tolerant, Kind-aware Targeting Classifier
def train_forward_looking_vt_pattern_classifier(self, profile_df, tolerance_pct=2.0, strategy = "meme", mae_thresh=90, fuzzy_thresh=85):
profile_df = profile_df.copy()
now = pd.Timestamp.now(tz=profile_df.index.tz)
this_monday = now.normalize() - pd.to_timedelta(now.weekday(), unit='D')
profile_df = profile_df[profile_df['week_start'] < this_monday]
reference_profile = profile_df.groupby('hour_of_week')['normalized_price'].mean().sort_index()
reference_array = reference_profile.to_numpy().reshape(1, -1)
expected_low_hour = reference_profile.idxmin()
# Compute base stats first (do this once, outside the loop)
avg_volatility = profile_df.groupby('week_start').apply(
lambda g: np.std(g.groupby('hour_of_week')['normalized_price'].mean())
).mean()
rows = []
for week_start, group in profile_df.groupby('week_start'):
week_series = group.groupby('hour_of_week')['normalized_price'].mean().sort_index()
week_series = week_series.reindex(reference_profile.index)
if week_series.isnull().any():
continue
week_array = week_series.to_numpy()
mae_score = 100 - np.mean(np.abs(week_array - reference_array.flatten()))
cosine_score = cosine_similarity(week_array.reshape(1, -1), reference_array)[0][0] * 100
# volatility-tolerant fuzzy score
# tolerance_abs = (tolerance_pct / 100) * reference_array.flatten()
# within_band = np.abs(week_array - reference_array.flatten()) <= tolerance_abs
# fuzzy_similarity_score = within_band.mean() * 100, percentage of matching points
week_volatility = np.std(week_array)
volatility_ratio = week_volatility / avg_volatility if avg_volatility > 0 else 1.0
# Clamp the scaling factor (to avoid exploding tolerance)
volatility_scale = np.clip(volatility_ratio, 0.5, 2.0)
scaled_tolerance_pct = tolerance_pct * volatility_scale
tolerance_abs = (scaled_tolerance_pct / 100) * reference_array.flatten()
within_band = np.abs(week_array - reference_array.flatten()) <= tolerance_abs
fuzzy_similarity_score = within_band.mean() * 100 # percentage of matching points
low_hour = week_series.idxmin()
high_hour = week_series.idxmax()
volatility = np.std(week_array)
range_percent = np.max(week_array) - np.min(week_array)
regime = self.get_week_market_regime(group)
rows.append({
'week_start': week_start,
'mae_score': mae_score,
'cosine_score': cosine_score,
'fuzzy_similarity_score': fuzzy_similarity_score,
'low_hour': low_hour,
'high_hour': high_hour,
'volatility': volatility,
'range_percent': range_percent,
'diff_from_pattern_low': abs(low_hour - expected_low_hour),
'regime': regime
})
df_feat = pd.DataFrame(rows).sort_values('week_start')
# Add rolling stats
for col in ['mae_score', 'cosine_score', 'fuzzy_similarity_score']:
df_feat[f'rolling_{col}_mean'] = df_feat[col].rolling(3).mean()
df_feat[f'rolling_{col}_std'] = df_feat[col].rolling(3).std()
# 🚨 define target according to strategy based on asset-kind (meme or trad) after all rolling features are added
mae_pass = df_feat['mae_score'].shift(-1) > mae_thresh
fuzzy_pass = df_feat.get('fuzzy_similarity_score', pd.Series([0]*len(df_feat))).shift(-1) > fuzzy_thresh
if strategy == 'meme':
df_feat['future_label'] = (mae_pass | fuzzy_pass).astype(int)
elif strategy == 'trad':
df_feat['future_label'] = mae_pass.astype(int)
else:
raise ValueError("Unknown strategy type. Choose 'meme' or 'trad'.")
df_feat = df_feat.dropna()
df_feat = pd.get_dummies(df_feat, columns=['regime'])
regime_cols = [col for col in df_feat.columns if col.startswith('regime_')]
features = [
'rolling_mae_score_mean', 'rolling_mae_score_std',
'rolling_cosine_score_mean', 'rolling_cosine_score_std',
'rolling_fuzzy_similarity_score_mean', 'rolling_fuzzy_similarity_score_std',
'volatility', 'range_percent', 'diff_from_pattern_low'
] + regime_cols
X = df_feat[features]
y = df_feat['future_label']
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X, y)
importances = model.feature_importances_
importance_df = pd.DataFrame({'feature': X.columns, 'importance': importances}).sort_values('importance', ascending=True)
fig = px.bar(importance_df, x='importance', y='feature', orientation='h',
title='🔮 Volatility-Tolerant Feature Importances (Random Forest)',
text=importance_df['importance'].round(3))
fig.update_layout(template='plotly_white', xaxis_range=[0, importance_df['importance'].max() * 1.1])
fig.update_traces(textposition='outside')
fig.show()
self.print_classifier_report(model, df_feat)
print("\n📈 Feature correlations with future_label:")
for col in features:
corr = df_feat[col].corr(df_feat['future_label'])
print(f"{col:<35} → {corr:+.3f}")
joblib.dump(
{'model': model, 'features': features},
f"{self.mrkt}_fuzzy_pattern_forward_classifier.pkl"
)
print("✅ Forward-looking volatility-tolerant classifier saved.")
PythonFactual Confirmation
In summary, we successfully derived a standard weekly price pattern, and through similarity matching, we demonstrated that this pattern is not merely a statistical abstraction—it manifests in real-world price behavior. Furthermore, we developed a volatility-tolerant classifier that can reasonably predict whether the upcoming week is likely to follow this established pattern. However, come Monday morning, we still lack certainty about how closely the current week is beginning to resemble the expected trajectory. Since we are particularly interested in early-week entry opportunities, we must now evaluate real-time behavior by downloading the most recent data at 15-minute intervals.
To facilitate a direct comparison, we must interpolate the reference pattern to match this higher-frequency resolution. Because we are aiming to identify an early-week price low as a potential entry point, we define our inspection window by locating the expected low hour from the reference pattern and setting this as the cutoff hour. To account for variation, we extend this window with a configurable time bandwidth, thus defining a more flexible time range.
Conditions
Before we proceed with the comparison, we validate that sufficient and current data is available. Then we normalize the live price data using the same method applied to the historical reference pattern. We then compare the early-week live segment against the reference pattern using the same metrics applied earlier in the Usability Check: MAE score to capture average deviation in shape plus Cosine similarity, to assess structural alignment. To increase confidence that a local price low has actually occurred, we incorporate two additional checks: A likely-low condition, which confirms that the minimum occurs near the end of the early-week window, and a non-negative price slope, indicating upward momentum following the low. A preliminary decision rule might therefore take the following form:
decision = (mae_score > 97 and cosine_score > 98.5 and likely_low and price_slope >= 0)
This rule formalizes the intuitive criteria: high similarity with the expected pattern, occurrence of a local minimum, and the onset of a rising trend—conditions well suited in theory for early-week entry signals in pattern-driven trading strategies.

Refining Decision Logic for Real-World Conditions
During empirical testing, we observed results such as those presented in the table above. While many assets achieved excellent shape alignment—with MAE scores above 98.5 and cosine similarity near 100%—they were nonetheless rejected due to rigid secondary criteria: either the local low did not occur in the final slot of the early-week window, or the price slope was only marginally negative. This revealed a limitation in the original decision rule. Although well-suited for idealized, textbook patterns, the combination of likely_low and price_slope ≥ 0 proved overly strict, especially in the presence of moderate volatility. In practice, this rigidity resulted in frequent false negatives—disqualifying weeks that broadly resembled the expected pattern but exhibited slight variations in timing or trend smoothness. To address this, we introduced a more flexible and interpretable scoring mechanism. Rather than enforcing binary thresholds, we compute a weighted similarity score, which combines structural alignment and directional signals. This enables more nuanced decision-making, balancing the core pattern resemblance with contextual features. The adjusted scoring formula we define as:
score = (
0.4 * np.clip(mae_score / 100, 0, 1) +
0.4 * np.clip(cosine_score / 100, 0, 1) +
0.1 * int(likely_low) +
0.1 * (1 if price_slope >= 0 else 0)
) * 100
This score emphasizes pattern similarity (80% combined weight for MAE and Cosine), softens the impact of timing and trend direction (20% combined for likely_low and price_slope) and supports more robust early-week decisions under real-world conditions. By comparing the resulting score against a configurable threshold (e.g., 85), the model can tolerate slight deformations in shape or timing—common in volatile markets—without compromising structural fidelity.
Factchecking the Prediction
# Evaluate whether early-week price is forming the expected low.
def evaluate_early_week_entry(self, live_df, reference_profile_hourly, cutoff_hour=3, band_width = 0, score_threshold = 85):
# 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
# 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 (default 03:00)
live_df = live_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)} data points on Monday before hour {end_hour}, e.g. cutoff_hour+band_width ({cutoff_hour}+{band_width})."
self.do_log(mess)
mess = f"From {live_df.index.min()} to {live_df.index.max()}"
self.do_log(mess)
if len(live_df) < 4:
mess = f"⚠️ Not enough data points for evaluation yet."
self.do_log(mess)
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.do_log(mess)
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]
likely_low = live_pattern.idxmin() == (len(live_pattern) - 1)
# Step 7: Apply decision criteria
score = (
0.4 * np.clip(mae_score / 100, 0, 1) +
0.4 * np.clip(cosine_score / 100, 0, 1) +
0.1 * int(likely_low) +
0.1 * (1 if price_slope >= 0 else 0)
) * 100
decision = score >= score_threshold
# score_details: dict with similarity scores & timing, decision: True if pattern matches and entry is likely valid
answ = 'No!'
if decision:
answ = 'Yes!'
mess = f"Decision: does the factual pattern resemble the default pattern based on mae- and cosines-scores? {answ}"
scores = f"mae_score': {round(mae_score, 2)}, cosine_score: {round(cosine_score, 2)}, price_slope: {round(price_slope, 3)}, likely_low_in: {likely_low}, live_pattern_len: {len(live_pattern)}"
return mess, decision, scores
PythonThis scoring framework shown above provides a practical decision-making mechanism for real-time entry assessment, especially suited to pattern-based strategies in volatile markets. With high-confidence predictions and real-time validations working in tandem, the system is now equipped to transition from conceptual framework to applied execution. In the next section, we integrate these components—reference pattern reconstruction, classifier-based forecasting, and early-week live confirmation—into a modular pipeline designed for autonomous, scheduled operation.
Putting it all Together
Having verified both the predictive and real-time discriminative power of weekly patterns, we are now in a position to operationalize these insights into an automated trading workflow. With robust standard weekly price patterns established, these can now serve as reliable reference profiles for similarity matching. In addition, we developed a forward-looking classifier, capable of forecasting whether the upcoming week is likely to follow the expected pattern. We also implemented a mechanism to verify whether early-week price behavior resembles the reference pattern in real time.
By integrating these components with essential utilities—for data acquisition, time-zone handling, JSON-based configuration loading, and structured logging—we implemented a modular Python class that serves as the foundation of a fully automated selection pipeline. This script efficiently processes a watchlist of assets and identifies suitable candidates for a trading signal generation bot.
The architecture combines early-week pattern recognition with intraweek signal generation, creating a layered decision system. This hybrid design proves especially valuable for meme coins, where correctly timing an entry just after a characteristic early-week low can significantly enhance trading outcomes.
Standalone Execution and Workflow Overview
To enable automated execution, we designed the Python class to function as a standalone script. This allows for scheduled runs—typically on Mondays and Tuesdays—using a task scheduler. For proper operation, the script requires access to a configuration file (in JSON format) that specifies the list of assets to process, along with any other required parameters.

Additionally, it is essential that a pretrained classifier model is available for each asset in the list. These models must be saved in the dedicated subdirectory .\\classifiers, as the script relies on loading them for predictive tasks. The script processes each asset in the watchlist sequentially, following a structured pipeline:
- Rebuild the Reference Pattern
For each asset, the script starts by rebuilding the reference profile using complete and up-to-date hourly price data. This includes downloading the dataset, performing data integrity checks, and preprocessing the information into a normalized Pandas DataFrame. - Check Entry Condition 1: Predicted Weekly Similarity
Using the method check_weekly_pattern_resemblance, the script loads the corresponding pretrained classifier and applies predict_next_week_similarity to estimate the likelihood that the upcoming week will follow the reference pattern. - Check Entry Condition 2: Factual Early-Week Resemblance
Next, the script downloads the most recent 15-minute interval data and uses evaluate_early_week_entry to assess whether the early-week price behavior conforms to the expected pattern—based on metrics such as MAE, cosine similarity, slope, and likely low detection. - Final Decision Logic
If both conditions—the predicted weekly similarity and the early-week factual resemblance—are satisfied, the asset is added to the bot list, indicating it is a viable candidate for signal-based trading execution.
Selecting Assets for Trading
def process_watch_list(self):
counter = 0
pred_hit = 0
fact_hit = 0
both_hit = 0
BOT_list = []
if not self.WATCH_LIST:
self.do_log("🚫 No assets in WATCH_LIST.")
return both_hit, BOT_list
for self.asset in self.WATCH_LIST:
counter += 1
print(f"{counter}.{self.asset}")
self.log_separator()
self.do_log("week-check started")
# step 1: --- Complete History and rebuild Reference weekly pattern for Asset---
if not self.get_data(self.asset):
self.do_log("❌ Failed to load data")
continue
self.do_log("✅ Complete and up-to-date dataset created")
if not self.get_dataframe():
self.do_log("❌ Failed to create dataframe from dataset")
continue
self.do_log("✅ Indexed dataframe created")
self.org_profile_df, self.reference_profile = self.create_reference_profile()
self.do_log("✅ Reference weekly pattern created")
# step 2: --- Check entry condition A New Week Probable Similarity Reference Pattern ---
model_path = Path('classifiers') / f'{self.mrkt}_fuzzy_pattern_forward_classifier.pkl'
a_probable_pattern = self.check_weekly_pattern_resemblance(self.org_profile_df, model_path, sim_threshold = self.sim_threshold)
if a_probable_pattern:
pred_hit += 1
# step 3: --- Check entry condition B Current Data Early Week Factual Resemblance ---
self.do_log(f"📥 Downloading most recent {self.FREQ} data from Binance")
success, self.current_df = self.download_data(self.asset, self.FREQ)
if not success:
self.do_log("❌ Failed to obtain current data")
continue
msg, b_factual_resemblance, scores = self.evaluate_early_week_entry(self.current_df, self.reference_profile, cutoff_hour=self.low_hour, band_width=self.band_width, score_threshold = self.score_threshold)
if b_factual_resemblance:
fact_hit += 1
self.do_log(msg)
self.do_log(scores)
# step 4: --- Evaluate combined entry condition Probability Confirmed ---
if a_probable_pattern and b_factual_resemblance:
both_hit += 1
BOT_list.append(self.asset)
self.do_log(f"✅ Both pattern prediction and early-week match passed. Asset {self.asset} added to Bot-list!")
start_bot = True
else:
self.do_log("❌ Entry conditions not met. Not on List for SignalBot.")
start_bot = False
# step 5: --- Visual diagnostics ---
self.plot_current_week_vs_average()
self.plot_live_vs_reference(self.current_df, self.reference_profile, cutoff_hour=self.low_hour, band_width=self.band_width)
if not start_bot:
continue # proceed to next asset
self.log_separator()
mess = f"Number of Assets processed: {counter}, pattern similarity predicted: {pred_hit}, factual resemblance found: {fact_hit}, both: {both_hit}"
self.write_log(self.log, mess+"\n")
return both_hit, BOT_list
PythonAutomating the Workflow
The class is designed to operate as a standalone module, making it easy to integrate into automated routines. It can be wrapped in a simple batch file and scheduled using Windows Task Scheduler, as outlined in Automate Your Edge: A DIY Crypto Signal Bot with Python and Windows Tools. Once triggered, the script processes the configured watch list, executing the full pipeline described earlier to filter candidate assets suitable for signal-based trading. These selected assets are then handed off to the trading engine—SignalBot—which monitors the market for these candidates in real time. This bot runs on a 15-minute interval, actively observing price behavior during the predefined window, which in this case spans early Monday through Tuesday afternoon.
Here is the method that runs the class and delivers its results to the config file of the bot SignalBot. At the end of the process, the filtered list of eligible assets is written to the configuration file used by SignalBot, ensuring a seamless handoff between pattern detection and live signal generation.
Run PatternSimilaritySignalBot
if __name__ == "__main__":
print('starting: pattern-similarity-signalbot.py')
settings_path = "settings.json"
WATCH_LIST = ['DOGE'] # Test dummy that will be overwritten from config.json at init
with open(settings_path, 'r') as f:
settings = json.load(f)
# Create and use the pattern analysis class
pssb = PatternSimilaritySignalBot(settings, WATCH_LIST)
if pssb.init_succes == True:
print('class instatiated and succesfull initialized: watch-list loaded')
countr, list = pssb.process_watch_list()
if countr:
print(f"PatternSimilaritySignalBot produced a List of {countr} Assets as Input for SignalBot")
with open(Path(pssb.signalbot_config_path), 'r') as f:
try:
data = json.load(f)
except json.JSONDecodeError:
raise ValueError("JSON file is malformed or empty.")
data['list'] = list
with open(Path(pssb.signalbot_config_path), 'w') as f:
json.dump(data, f, indent=4)
else:
sys.exit()
PythonLooking Forward
We demonstrated a Python pattern recognition class that serves as the foundation of a fully automated selection pipeline The architecture combines early-week pattern recognition with intraweek signal generation thus creating a layered decision system. The concept is clear and it actually works. That does not mean however our work is done. On both sides of the pipeline there’s room for improvements and finetuning. Also a major upgrade would be to make the SignalBot smarter. The current implementation is stateless; the signaling decision process could improve substantially by providing the bot with a memory and adding intelligence. This will be discussed later in a separate research paper.