Forecasting Stock Prices with LSTMs, SVMs & AI Models (Complete Python Guide)
A systematic review and implementation blueprint analyzing deep learning, support vector machines, and multimodal financial data.
This 2024 review article synthesizes ten systematic reviews of AI-based stock-market prediction. It examines which prediction methods, informational sources, and evaluation metrics are most common, using a PRISMA-guided search of Scopus and Web of Science. The synthesis emphasizes SVM, LSTM, and neural networks; historical closing-price time series and technical indicators; and metrics such as accuracy, MSE, RMSE, MAPE, and MAE. It recommends combining numerical, textual, sentiment, financial, macroeconomic, spatial, and temporal information, while recognizing trade-offs involving data requirements, complexity, interpretability, robustness, and computational cost. The article does not define or train a new predictive architecture, and therefore cannot directly yield a faithful end-to-end model implementation without additional implementation decisions outside the paper.
Download The Source Code Using the URL At the End of this article!
Research paper: https://www.sciencedirect.com/science/article/pii/S2590291124000615?ref=pdf_download&fr=RR-2&rr=a2a5647f6ff1e2c9
Implementation Assumptions
The implementation is a literature-review synthesis package, not a stock-prediction model implementation.
Bibliographic search results and full texts are supplied locally or manually; no live Scopus, Web of Science, Rayyan, Yahoo Finance, or other external API is required.
The ten retained reviews are represented as review metadata and evidence records; they are not treated as primary stock-price datasets.
Missing included-study counts and unresolved Table 5 alignments remain explicit None or uncertainty values.
Metric names are cataloged but no formulas are implemented because the supplied paper context contains no canonical equations.
No prevalence is recomputed across incompatible denominators or review subsets.
Any future predictive reproduction must supply task, target, horizon, split, preprocessing, alignment, architecture, hyperparameters, optimization, and evaluation decisions externally.
Python 3.11+ standard-library dataclasses, enums, pathlib, argparse, typing, datetime, and json are sufficient for the planned local package.
Synthetic records are used only for demonstration when original bibliographic exports are unavailable.
The target code-file average is approximately 400 lines, with focused modules allowed to be shorter where the domain responsibility is narrow; artificial padding is not planned.
Scope and the paper-faithful implementation boundary
What should be reproduced when a paper reviews other reviews instead of proposing a trainable model? The practical answer is not an invented LSTM, SVM, or multimodal network. For this paper, reproduction means preserving the review-selection process, the extracted evidence, the uncertainty around that evidence, and the boundaries of what the source does not specify.
Start with the paper’s actual object of study
Paper fact. The source is a systematic review of systematic reviews about artificial-intelligence methods for stock-market price or return prediction. Its research questions ask three separate questions: which prediction methods are commonly reported, which information sources are commonly used, and which evaluation metrics are reported. The paper searches Scopus and Web of Science, screens candidate systematic reviews, retains ten reviews, and synthesizes their findings.
That distinction matters. A review-level statement that LSTM is frequently discussed is evidence about the reviewed literature. It is not a specification of an LSTM’s input shape, sequence length, hidden-state size, loss, optimizer, or training schedule. Similarly, a reported mention of RMSE tells us that a metric was used in some reviewed studies; it does not supply a formula that this package should reconstruct.
Derived implementation explanation. The generated package therefore has four responsibilities:
prisma_review_screeningrepresents the search, screening, duplicate handling, and final-eligibility workflow.review_evidence_extractionconverts review-level observations into records with provenance and uncertainty.review_evidence_synthesisanswers the three review questions descriptively while preserving each source subset and denominator.predictive_reproduction_contractidentifies the external decisions required before any separate stock-prediction implementation could begin.
These responsibilities produce catalogs, screening logs, claims, warnings, and contract reports—not model weights or predictions.
Keep reported counts source-qualified
The paper reports 40 records from Scopus and 29 from Web of Science. It also reports a screening flow of 69 retrieved titles, 43 reviews read, 17 remaining after abstract screening, 16 after duplicate or same-author handling, and 10 final reviews. These numbers describe different database sources or workflow stages, so the implementation stores them separately.
The generated constants make that distinction visible:
PAPER_ID: Final[str] = "1-s2.0-S2590291124000615-main"
"""Identifier of the supplied paper."""
REPORTED_REVIEW_COUNT: Final[int] = 10
"""Number of systematic reviews retained in the reported final corpus."""
REPORTED_RETRIEVAL_COUNTS: Final[Mapping[str, int]] = MappingProxyType(
{
"Scopus": 40,
"Web of Science": 29,
}
)PAPER_ID identifies the source artifact. REPORTED_REVIEW_COUNT records the ten retained reviews. REPORTED_RETRIEVAL_COUNTS preserves database provenance rather than silently treating 40 and 29 as interchangeable observations.
The same principle applies to percentages and study counts. The ten retained reviews collectively cover more than 379 primary studies according to the abstract, while other findings use subsets such as 12, 30, 34, 45, 57, or 122 studies. Those denominators cannot be combined into one prevalence estimate. The code’s evidence and claim objects therefore keep the originating subset and denominator attached to each observation.
REPORTED_FLOW_COUNTS: Final[Mapping[str, int]] = MappingProxyType(
{
"retrieved": 69,
"reviews_read": 43,
"abstract_remaining": 17,
"post_duplicate": 16,
"final_retained": 10,
}
)These are workflow facts, not predictive-study counts. A local reproduction may calculate its own FlowCounts, but comparison with the paper’s values should report differences rather than force the local records to match them.
Use local records instead of pretending to query databases
Implementation decision. The package does not expose live Scopus or Web of Science credentials, APIs, or network behavior. Bibliographic records are supplied externally, for example through a local export or manually assembled input. This is consistent with the paper’s protocol without inventing an API contract that the paper never defines.
The central orchestration function makes this boundary explicit:
def run_review_pipeline(
config: SearchConfiguration,
candidates: Sequence[CandidateRecord],
batches: Sequence[ExtractionBatch],
decisions: Sequence[tuple[str, ScreeningDecision]],
) -> PipelineResult:config describes the paper-reported search protocol. candidates contains already available bibliographic records, retaining fields such as title, abstract, database, and retrieval metadata. batches contains review-level extraction inputs. decisions supplies explicit screening decisions, including their stage and reason. The returned PipelineResult links the catalog, screening log, flow counts, synthesis report, and missing-specification report.
The function validates candidate identities and decision references before calling prisma_review_screening. It then codes extraction batches, builds a ReviewCatalog, and invokes synthesize_review. Importantly, this sequence never calls a forward pass, loss function, optimizer, or metric implementation.
The package exceptions describe this boundary as contract validation:
class ReviewSynthesisError(Exception):
"""Base exception for package-level contract violations."""
class ValidationError(ReviewSynthesisError):
"""Raised when an input value or workflow invariant is invalid."""
class IncompleteSpecificationError(ReviewSynthesisError):
"""Raised when externally required predictive details are missing."""ValidationError represents invalid local data, such as a missing title or a decision referring to an unknown candidate. IncompleteSpecificationError is different: it means that a requested predictive reproduction lacks information that the paper does not supply. Neither exception represents failed model training, because the package does not define a predictive-model runtime.
Worked example: a review report without a stock forecaster
Suppose two bibliographic records arrive from different databases. The local caller supplies title and abstract metadata, then records title/abstract and final-eligibility decisions. The caller can request the workflow through the package API:
config = default_paper_search_config()
result = run_review_pipeline(config, candidates, batches, decisions)In this example, candidates are local CandidateRecord objects, batches contain observations such as reported methods or metrics, and decisions are ScreeningDecision objects. The result can be inspected through result.screening_log, result.flow_counts, and result.synthesis_report. A screening log answers questions such as which stage changed a record’s status and why. The synthesis report answers the methods, information-source, and metric questions using the supplied coded evidence.
This is intentionally not equivalent to training a model. The paper names methods including SVM, SVR, LSTM, RNN, ANN, CNN, ARIMA, ANFIS, and hybrid approaches, but it does not unify them into one executable interface. It also mentions tools such as Python, TensorFlow, Pandas, NumPy, Keras, Scikit-Learn, MATLAB, TA-Lib, and TA4J without prescribing a required dependency stack or API. The generated implementation consequently treats those names as literature evidence or possible external tooling, not as mandatory runtime components.
Make missing predictive details impossible to overlook
A separate caller may still want to build a stock predictor based on one of the reviewed method families. That is a different implementation task. The package provides run_predictive_contract_only as a gate around that task:
def run_predictive_contract_only(config: PredictiveConfiguration) -> dict[str, object]:
"""Validate an external predictive specification without training anything.
This implements the predictive-reproduction contract as a configuration
gate. Missing fields are returned explicitly; no target, horizon,
preprocessing rule, architecture, optimizer, or metric formula is chosen
by this function.
"""The external PredictiveConfiguration must state decisions such as classification versus regression, target definition, forecast horizon, input modalities, sequence or lookback construction, split policy, scaling policy, model family, hyperparameters, optimizer, loss, and evaluation metrics. Multimodal inputs also need explicit temporal alignment and leakage-prevention rules.
Implementation decision. If a configuration omits the target or horizon, the package must report that omission rather than select a default. It must not assume that daily data, an approximately 1000-day period, a particular normalization policy, or an LSTM architecture is universal. An incomplete configuration leads conceptually to an IncompleteSpecificationError and a missing-specification report; it does not silently become a runnable predictor.
The local command-line interface follows the same boundary. Documentation may show an invocation such as:
python -m review_synthesis.cli --example --output report.jsonThe main function supports deterministic examples or local JSON input and can render JSON, Markdown, or text. The example records are synthetic fixtures for demonstrating the data flow, not the paper’s original database export and not newly measured predictive results.
Boundary statement
The paper-faithful reproduction is the provenance-preserving review evidence workflow: search configuration, screening records, coded observations, source-qualified claims, and uncertainty warnings. Any end-to-end stock-prediction model requires an external specification for its target, horizon, preprocessing, architecture, optimization, and evaluation procedure. Those details cannot be recovered from this review without inventing content beyond the supplied source.
Domain records, provenance, and uncertainty
How can a local Python package keep a review finding attached to the place where it was observed? Treat every item as an evidence envelope. A candidate review has an identity, database source, and retrieval metadata. An extracted observation has a dimension, wording, confidence, and paper location. A quantitative claim also retains its denominator and review subset. This design prevents a label such as SVM, RMSE, or historical closing prices from becoming detached from its source.
Separate controlled labels from free text
Paper fact. The paper discusses several method families, information sources, and metrics, but it does not define one executable predictor. The implementation therefore needs labels for cataloging evidence, not tensor types or model layers.
The generated enums.py module provides those labels. Database identifies whether a candidate came from Scopus or Web of Science. ScreeningStage distinguishes retrieval, title-and-abstract screening, duplicate handling, same-author handling, full-text review, and final eligibility. Decision records inclusion, exclusion, or a pending state. EvidenceDimension separates methods, information sources, metrics, tools, and datasets. Confidence distinguishes reported, uncertain, and missing evidence.
TaskType also appears in the module, with classification and regression values, but it belongs to an external predictive specification. It does not define a target, label rule, or forecast horizon for this paper.
The enum values are string-compatible, which gives serialized records stable values such as "scopus", "method", and "uncertain". The important implementation decision is that these labels do not replace the source wording. They organize it while leaving room for ambiguity.
Provenance is data, not a comment
In this workflow, provenance means the information needed to answer “where did this value come from?” SourceLocation identifies a location in the supplied paper context. Its paper_location can identify a section, table, or figure, while section_id and table_id preserve more specific identifiers when available.
Provenance carries a different kind of lineage. Its database and retrieval_date describe bibliographic acquisition; its source_location and original_text describe the paper evidence itself. These concerns are deliberately separate because a coded observation may have a paper location without having come from a live database query.
The following excerpt is the generated definition of the paper-location record:
@dataclass(frozen=True)
class SourceLocation:
"""Identify where an extracted fact appears in the supplied paper context.
``paper_location`` is intentionally a free-form label because the source
includes section identifiers, table identifiers, and figure identifiers.
``section_id`` and ``table_id`` preserve the more specific identifiers when
they are available.
"""
paper_location: str
section_id: Optional[str] = None
table_id: Optional[str] = NoneThe dataclass is frozen so callers cannot casually mutate the location after an evidence object has captured it. Its fields are textual identifiers, not coordinates inferred from the damaged PDF extraction. The generated __post_init__ rejects an empty paper_location and rejects empty optional identifiers when they are supplied.
require_provenance is the explicit boundary check for extracted evidence. It requires a valid Provenance object and a non-empty source_location; it does not invent a location or repair Table 5 alignment. This is important because the supplied extraction says that Table 5 method, source, and metric associations are fragmented and not fully reliable row by row.
Candidate records describe literature, not market data
CandidateRecord represents one bibliographic candidate received from an external export. Its required fields are title and database. It can also retain a stable record_id, abstract, publication metadata, authors, query text, and arbitrary metadata. The provenance field preserves retrieval lineage.
This is the central distinction: a CandidateRecord is not a stock-price sample. It does not contain a time axis of observations, feature columns, labels, or model inputs. It is an envelope around a publication record that will later receive screening decisions.
A focused construction using the generated public classes looks like this:
from datetime import date
from review_synthesis.enums import Database
from review_synthesis.provenance import Provenance, SourceLocation
from review_synthesis.records import CandidateRecord
location = SourceLocation(
paper_location="Table 5",
section_id="1-s2.0-S2590291124000615-main_sec_016",
table_id="Table 5",
)
provenance = Provenance(
database=Database.SCOPUS,
retrieval_date=date(2022, 4, 11),
source_location=location,
)
record = CandidateRecord(
record_id="candidate-1",
title="Illustrative systematic review",
database=Database.SCOPUS,
provenance=provenance,
)Here, Database.SCOPUS is the controlled source label, while the date is a Python datetime.date. The example is a local illustrative record, not one of the paper’s original database exports. The SourceLocation points to the supplied paper context, whereas the provenance’s database and retrieval date describe how the candidate record was acquired.
CandidateRecord derives an identifier only when record_id is omitted. It does not collapse records from different databases, and it does not decide whether a title is eligible. Those are separate workflow operations.
Screening decisions preserve state and reasons
A ScreeningDecision records one decision at one stage. Its stage might be ScreeningStage.TITLE_ABSTRACT or ScreeningStage.FINAL_ELIGIBILITY; its decision might be Decision.INCLUDE, Decision.EXCLUDE, or Decision.PENDING. Every decision has a non-empty reason, and it may also record a reviewer and timestamp.
For example, a local screening log could receive this decision:
from review_synthesis.enums import Decision, ScreeningStage
from review_synthesis.records import ScreeningDecision
decision = ScreeningDecision(
stage=ScreeningStage.TITLE_ABSTRACT,
decision=Decision.INCLUDE,
reason="title and abstract identify a systematic review of AI stock prediction",
reviewer="reviewer-1",
)The reason is not decorative. It makes an exclusion auditable and distinguishes “excluded because it concerns portfolio optimization” from “pending because the abstract is unavailable.” The generated model permits a pending decision, but still requires a reason explaining why the record remains unresolved.
ReviewRecord serves a later boundary: it stores metadata for a retained systematic review and its reported included-study count. The count is positive when known and None when the supplied paper leaves it unspecified. None therefore means “not supplied,” not zero and not an estimate. The record remains review metadata, not a primary stock-prediction dataset.
Coded evidence keeps wording, coding, and uncertainty together
CodedEvidence represents one review-level observation. Its dimension says what kind of observation it is. canonical_label is a controlled label used for grouping, while original_text preserves the wording that was actually extracted. confidence records whether the association is reported, uncertain, or missing. source_review_id links the observation to its retained review, and denominator preserves a reported positive study count when one exists.
The distinction between the two text fields matters. A controlled vocabulary might map “support vector machine” to SVM, but the source wording must remain available for audit. For a fragmented Table 5 association, the canonical label might be SVM while the confidence is Confidence.UNCERTAIN.
A worked example for the planned evidence item is:
from review_synthesis.enums import Confidence, EvidenceDimension
from review_synthesis.records import CodedEvidence
svm_evidence = CodedEvidence(
dimension=EvidenceDimension.METHOD,
canonical_label="SVM",
original_text="SVM",
confidence=Confidence.UNCERTAIN,
source_review_id="review-1",
provenance=provenance,
)This item says only that the review-level coding recorded SVM at the supplied location with uncertain confidence. It does not say that the current package trained an SVM, that SVM was best, or that the Table 5 row alignment is certain.
EvidenceClaim is the appropriate object for a broader qualitative or quantitative statement. In addition to its claim text and optional source review, it stores subset_id, denominator, provenance, and confidence. The subset_id is essential because the paper reports findings over different review subsets. A claim about 12 studies must not be silently combined with a claim about 57, 122, or more than 379 studies.
Validate at module boundaries
The generated validation.py exposes four focused functions:
validate_candidate(record)checks candidate identity, title, database, provenance type, optional publication fields, authors, and metadata.validate_screening_decision(decision)checks the stage, decision, reason, reviewer, and timestamp.validate_review_record(record)checks review identity, optional metadata, and the included-study count.validate_evidence(item)checks the evidence dimension, labels, confidence, source review, denominator, and provenance.
These functions return None for valid input and raise ValidationError for invalid structure or values. Their None handling is deliberate. Optional fields such as an abstract, publication date, included-study count, and denominator may be absent because the source context does not provide them. Validation rejects malformed values, but it does not manufacture missing information.
validate_evidence goes one step further by calling require_provenance. Consequently, a coded evidence item without a SourceLocation is rejected rather than assigned a guessed section or table. This is the implementation expression of the paper’s uncertainty around extracted table alignment.
The generated code was not executed or verified under the authoritative run policy. The records and validators described here are implementation artifacts whose intended responsibility is to make provenance, uncertainty, and missing values explicit. The boundary remains firm: these objects model literature evidence and review workflow state, not numerical tensors or a trained stock-prediction model.
Configurable PRISMA-style retrieval and screening
How do you reproduce a review-selection process when the original databases and full-text decisions are not available inside the Python package? Separate acquisition from screening. Bibliographic records arrive from an external export or manual process; the local package then validates, screens, deduplicates, logs, and reports them. This keeps the workflow auditable without pretending that the package performed a live database query.
Start with an explicit search protocol
Paper fact. The paper reports a PRISMA-guided search of Scopus and Web of Science. Its search was title-based, restricted to articles, used variants of “Systematic Review,” “Systematic Literature Review,” and “Stock,” and had a final search date of April 11, 2022. The reported publication window begins in 2009.
The generated SearchConfiguration records these choices. Calling default_paper_search_config() creates a configuration with separate Database.SCOPUS and Database.WEB_OF_SCIENCE values, title_only=True, article_only=True, the three search terms, and an InclusionCriteria object. That criteria object includes the English-language requirement, publication window, AI-primary-use requirement, stock-market scope, excluded facets such as volatility and portfolio optimization, and a configurable geographic policy.
The geographic policy is deliberately not hard-coded as an unquestionable filter. The supplied paper mentions studies involving markets such as Taiwan, NASDAQ, Dow Jones, and European markets while also stating a US, UK, and Europe criterion. Because the source does not reconcile those statements, geography_policy remains configurable. This is an implementation decision that exposes an ambiguity instead of silently resolving it.
The default construction is local configuration only:
return SearchConfiguration(
databases=frozenset({Database.SCOPUS, Database.WEB_OF_SCIENCE}),
title_only=True,
article_only=True,
terms=["Systematic Review", "Systematic Literature Review", "Stock"],
final_search_date=date(2022, 4, 11),
criteria=criteria,
)validate_search_config(config) checks that the dates, databases, terms, flags, and criteria are structurally valid. It does not contact Scopus or Web of Science, and it does not supply credentials, query syntax, or API behavior. The paper names those databases as sources, but it does not provide a required software API for accessing them.
Preserve raw retrieval provenance
Records obtained from an external database export are converted with make_candidate(record_id, title, database, retrieval_date, abstract=None, metadata=None). The resulting CandidateRecord retains the source database, retrieval date, title, optional abstract, query metadata, and a stable record identifier. The function validates the required identity fields and does not reinterpret a record as a stock-price dataset.
The next operation is intentionally conservative. merge_raw_records(records) combines records without deduplicating them:
def merge_raw_records(records: Sequence[CandidateRecord]) -> list[CandidateRecord]:
"""Combine raw records without deduplication or metadata rewriting.
The returned list preserves input order, including duplicate publications
returned by different databases. Each record retains its original
database and provenance; duplicate handling belongs to a later workflow
stage.
"""This separation matters because the paper reports 40 Scopus records and 29 Web of Science records, for a reported total of 69 retrieved records. reported_retrieval_counts(records) returns source-specific counts and a separately named raw_total; it does not turn records with the same title into one record. Raw retrieval counts, deduplicated counts, and retained-review counts therefore remain different quantities.
Screen with explicit include, exclude, and pending states
PRISMA is a reporting framework for systematic-review selection. Here, screening means applying eligibility rules in stages and recording the result. The package uses ScreeningStage values for retrieval, title-and-abstract screening, duplicate handling, same-author handling, full-text assessment, and final eligibility. Each ScreeningDecision contains a stage, a Decision value, a reason, and optional reviewer information.
screen_title_abstract(record, criteria) applies the available title, abstract, and explicit metadata. screen_full_text(record, full_text, criteria) applies the same kind of conservative checks to supplied full text. exclusion_reason(record, stage, criteria) exposes the reason-finding part of this process without necessarily producing a decision.
The important failure case is missing evidence. An absent abstract is not treated as proof that a record is irrelevant. Missing required metadata can produce Decision.PENDING, rather than an invented inclusion or exclusion. Likewise, an empty full-text string produces a pending full-text decision. This behavior is an implementation policy designed to avoid converting unavailable information into negative evidence; it is not a claim that the paper supplied these exact keyword rules.
The generated screening functions use transparent local checks for review terminology, AI-related terminology, stock-market scope, publication metadata, and configured excluded facets. They also avoid inferring geography from a market name. A caller can therefore replace or supplement these rules with manually reviewed decisions while retaining the same record and log contracts.
Keep duplicate and same-author handling separate
Duplicate removal and same-author exclusion are related but distinct workflow events. deduplicate_candidates(records) creates a normalized comparison key from the title, retains the first-seen candidate, and returns explicit ScreeningDecision objects at the DUPLICATE stage for later matches. The retained record keeps its original database provenance.
apply_same_author_policy(records, policy) does not guess which same-author publication should be excluded. The supplied paper reports such a stage but does not define a reproducible automatic selection rule. The generated function accepts explicit policies such as "manual" or "none"; an actual exclusion must be supplied separately as a logged decision. This prevents an undocumented heuristic from being mistaken for a paper fact.
Every decision is stored through ScreeningLog.append(record_id, decision). The log preserves the order of decisions, allows multiple stages for the same record, and rejects contradictory final decisions at one stage. For example, a pending title-and-abstract decision may later be replaced by an explicit decision, but two different non-pending decisions at that same stage are treated as a contract violation.
Worked example: two local candidates
Suppose a local export contains one candidate from each database. The records can be created with the exact public constructor described above:
from datetime import date
from review_synthesis.enums import Database
from review_synthesis.search_records import make_candidate
scopus_record = make_candidate(
"scopus-1",
"Systematic Review of AI Stock Prediction",
Database.SCOPUS,
date(2022, 4, 11),
)
wos_record = make_candidate(
"wos-1",
"Systematic Review of AI Stock Prediction",
Database.WEB_OF_SCIENCE,
date(2022, 4, 11),
)These two records have identical displayed titles but remain separate raw records because they came from different databases. Passing them to merge_raw_records preserves both. Passing them to deduplicate_candidates identifies the second title as a duplicate and returns a DUPLICATE exclusion decision for it. The decision can then be appended to a ScreeningLog with log.append("wos-1", decision).
For a complete local workflow, the caller supplies title-and-abstract and final-eligibility decisions, then invokes prisma_review_screening(config, records, decisions). The function validates the search configuration and records, logs each supplied decision, computes stage counts, and converts candidates with explicit unblocked final inclusion into ReviewRecord metadata. It does not query either database or decide unavailable full-text eligibility.
After the call, screening_log.decisions_for("scopus-1") returns the ordered audit trail for that record. screening_status("scopus-1", screening_log) reports the decision at the furthest logged workflow stage. retain_eligible_records(records, screening_log) requires an explicit FINAL_ELIGIBILITY inclusion and rejects any candidate with an earlier exclusion, including duplicate or same-author exclusion.
Interpret flow counts without forcing reconciliation
FlowCounts stores five stage-specific values: retrieved, reviews_read, abstract_remaining, post_duplicate, and final_retained. compute_flow_counts(records, log) calculates these values only from records and explicit logged decisions. It does not fill missing stages or infer that a pending record survived screening.
Paper fact. The reported flow is 69 retrieved titles, 43 reviews read, 17 remaining after abstract-based criteria, 16 after duplicate or same-author handling, and 10 final reviews. These values describe the paper’s review workflow, not a stock-prediction training set.
A local demonstration with two records will not reproduce those counts, and it should not be padded or adjusted to do so. Instead, construct a separate FlowCounts value for the reported flow and compare it with the locally calculated value using compare_with_reported_flow(actual, reported). The result contains stage-by-stage differences; it does not overwrite local records or merge incompatible counts.
The same rule applies to study totals and percentages. Counts such as 12, 30, 34, 45, 57, 122, and more than 379 belong to different review subsets or scopes. A screening implementation must retain their source context rather than calculate one combined prevalence estimate.
Boundary of this workflow
This package implements a local, provenance-preserving screening boundary. Search acquisition, unavailable full-text judgments, and any manual resolution of ambiguous records remain external inputs. The generated code also does not train SVM, LSTM, ANN, CNN, ARIMA, or another predictor. That limitation is intentional: the paper reports a synthesis of existing studies, not a complete predictive architecture or reproducible training procedure.
The practical result is an auditable path from externally supplied records to retained review metadata and stage-specific flow counts. It reproduces the review-selection responsibilities supported by the source while leaving unresolved evidence visibly unresolved.
Evidence extraction and uncertainty-aware coding
How should a program record that a systematic review mentioned LSTM, historical closing prices, or RMSE without turning that mention into a complete predictive model? Treat extraction as transcription with a chain of custody. Each observation keeps its original wording, evidence category, source review, paper location, denominator when available, and confidence. The package therefore catalogs literature evidence; it does not infer a model architecture, calculate a metric, or reconstruct a damaged table row.
Code review-level fields, not predictive tensors
Paper fact. The paper reports method categories including SVM, SVR, LSTM, RNN, ANN, CNN, DNN, MLP, GA-SVR, ANFIS, ARIMA, PCA, clustering, text mining, sentiment analysis, technical analysis, ensemble methods, and related approaches. It also reports information sources such as historical prices, technical indicators, financial or macroeconomic variables, news, Twitter, sentiment indices, and other markets. These are review findings, not a unified feature matrix.
The generated ExtractionInput represents one extracted field for one retained review. Its review_id links the field to the review record; field selects one of methods, sources, metrics, tools, or datasets; values is a sequence of reported labels; source_location identifies where the extraction came from; denominator preserves a reported study subset size; and confidence distinguishes reported, uncertain, and missing information. An empty values sequence is valid only when confidence is Confidence.MISSING, so absence is explicit rather than silently converted into an empty finding.
ExtractionBatch groups these inputs for one review. Its methods, sources, metrics, tools, and datasets collections are dimension-specific. During construction, each item must belong to the batch's review_id and have the expected EvidenceDimension. This is an important invariant: a metric extracted for one review cannot accidentally be attached to another review merely because two records happened to be processed next to each other.
The following excerpt shows the core shape contract. It is deliberately a record of labels and provenance, not a numerical array or model input tensor.
@dataclass(frozen=True)
class ExtractionInput:
"""One provenance-qualified, review-level extraction field.
``values`` contains labels reported for a review field. It does not encode
row alignment among methods, inputs, or metrics from a fragmented table.
"""
review_id: str
field: str
values: tuple[str, ...] = ()
source_location: SourceLocation = dc_field(
default=None # type: ignore[assignment]
)
original_text: Optional[str] = None
denominator: Optional[int] = None
confidence: Confidence = Confidence.REPORTEDHere, values is a tuple of strings, not a time-series tensor. source_location is mandatory for an extraction, while original_text can preserve a larger source phrase. A positive integer denominator is copied as metadata; it is not used to manufacture a percentage. validate_extraction_input checks these requirements and raises ValidationError for an invalid structure.
Use conservative, dimension-specific vocabulary
VocabularyEntry maps a canonical label to explicitly configured aliases within one EvidenceDimension. For example, the vocabulary can map “support vector machine” to SVM in the method dimension, or “mean absolute error” to MAE in the metric dimension. The mapping is intentionally exact after only whitespace and case normalization. An unknown term remains unchanged and receives Confidence.UNCERTAIN.
This dimension boundary matters for ambiguous labels. A notation such as R should not be interpreted as a metric, method, or information source unless that interpretation has been explicitly configured. Likewise, the extracted abbreviation CPI and the phrase “Customer Pricing Index” are not silently corrected. The supplied paper context flags that wording as potentially erroneous, but does not authorize the implementation to replace it with “Consumer Price Index.”
The default vocabulary contains conservative paper-mentioned labels for method families, sources, metric names, tools, and named datasets. It preserves the distinction between similar neural-network labels such as NN and ANN unless an explicit alias says otherwise. This prevents a convenient normalization rule from making a stronger claim than the source supports.
Delegate field extraction to provenance-aware coding
The public extractors are intentionally narrow. extract_methods selects only the method inputs, extract_information_sources selects only source inputs, and extract_metrics selects only metric inputs. They all delegate to code_extraction_batch, which applies vocabulary mapping, validates each input, copies the review identifier and denominator, and calls mark_fragmented_table_evidence.
The following excerpt is the generated metric extractor. Notice that it returns coded evidence and explicitly documents that formulas are not reconstructed.
def extract_metrics(
batch: ExtractionBatch,
vocabulary: Sequence[VocabularyEntry],
) -> list[CodedEvidence]:
"""Extract metric names only, without reconstructing or evaluating formulas."""
return _extract_dimension(batch, EvidenceDimension.METRIC, vocabulary)The return value is a list of CodedEvidence objects. Each object contains a canonical_label, the exact original_text, a source_review_id, a confidence, optional denominator, and provenance. Thus RMSE is cataloged as a metric name only. No formula for RMSE, MSE, MAE, MAPE, NMSE, correlation coefficient R, accuracy, precision, recall, F1-score, or POCID is supplied or inferred here.
extract_tools_and_datasets returns both tool and dataset evidence, but the individual objects retain their distinct dimensions. This allows Python, TensorFlow, Yahoo Finance, TAIEX, NASDAQ, and Dow Jones to remain separate kinds of reported evidence rather than being treated as interchangeable inputs or dependencies.
Mark fragmented Table 5 associations as uncertain
Paper fact. The extracted Table 5 content is fragmented by PDF layout. Method, information-source, and metric entries do not have reliably recoverable row alignment in the supplied context. A label may therefore be reported in the same table area as another label without proving that the two belonged to the same review row or primary study.
The implementation responds by preserving the association but lowering its confidence when its provenance points to Table 5. mark_fragmented_table_evidence does not change the label, denominator, review identifier, or source location. It changes only a Confidence.REPORTED value to Confidence.UNCERTAIN for affected evidence. Existing uncertainty is never upgraded.
This is safer than inferring alignment from extraction order. For example, if one uncertain Table 5 extraction contains SVM, historical closing prices, and RMSE, the resulting records can retain all three observations while making clear that the source does not establish a method–input–metric relationship. The package does not create a study-level row from those three strings.
Worked example: one uncertain extraction batch
Suppose an external transcription creates an ExtractionBatch for a retained review with three ExtractionInput items: SVM in the methods field, historical closing prices in sources, and RMSE in metrics. Each item uses a SourceLocation whose paper_location or table_id identifies Table 5, and each item carries Confidence.UNCERTAIN because the table alignment is unresolved.
The intended local flow is:
Construct the three
ExtractionInputrecords with the same review identifier.Group them into one
ExtractionBatch.Obtain the configured list from
default_vocabulary().Pass the batch to
code_extraction_batch, or callextract_methods,extract_information_sources, andextract_metricsseparately.Inspect each resulting
CodedEvidenceobject.
The method evidence may have canonical label SVM and original text SVM; the source evidence may have canonical label historical closing prices; and the metric evidence may have canonical label RMSE. All three retain the same originating review only if the input batch declared that review, and all three retain the Table 5 provenance. Their confidence remains uncertain. The code does not claim that SVM used those prices or that RMSE evaluated that method.
For a term that cannot be normalized, preserve_unresolved_term provides the same conservative behavior. A call with the extracted CPI wording produces a CodedEvidence whose canonical label is still the original phrase and whose confidence is Confidence.UNCERTAIN. It does not silently rewrite the paper's terminology:
def preserve_unresolved_term(
original_text: str,
dimension: EvidenceDimension,
) -> CodedEvidence:
"""Create uncertain evidence without normalizing an unresolved term."""
original = _clean_text(original_text, "original_text")
if not isinstance(dimension, EvidenceDimension):
raise ValidationError("dimension must be an EvidenceDimension value")
return CodedEvidence(
dimension=dimension,
canonical_label=original,
original_text=original,
confidence=Confidence.UNCERTAIN,
source_review_id="unresolved",
provenance=Provenance(original_text=original),
)The source_review_id="unresolved" value signals that this helper is for an unresolved term rather than a fully attributed review extraction. For normal extraction, code_extraction_batch preserves the actual review identifier and requires source provenance through validate_extraction_input and require_provenance.
Preserve the reported ten-review corpus
reported_review_corpus creates metadata records for the ten retained systematic reviews described in Table 4. These records are not stock-price datasets. They are review-level objects used to connect extracted methods, sources, metrics, tools, datasets, and claims.
The reported included-study sequence is 27, unspecified, 53, 12, 34, 122, 24, 57, 30, and 20. In Python, the unspecified entry remains None; it is not estimated, replaced with zero, or inferred from another review. validate_review_corpus checks the ten-record fixture and this missingness invariant. The sequence should therefore be understood as metadata attached to ten different reviews, not as ten compatible samples that can be summed or averaged without an explicit research design.
Keep qualitative findings separate from measured results
The paper also discusses limitations and future recommendations, including multimodal data integration, sentiment and text use, hyperparameter analysis, interpretability, robustness, scalability, individual-company prediction, and cross-market evaluation. QualitativeFinding stores such text with its category, source review, source location, and confidence.
collect_limitations and collect_recommendations use conservative textual cues to organize already supplied claims. group_qualitative_findings groups them by category without ranking them. These functions do not turn a recommendation such as “combine textual and numerical inputs” into an implemented multimodal architecture, and they do not turn a reported limitation into a measured performance result.
The practical boundary is therefore clear: extraction can tell us that the reviewed literature mentions SVM, LSTM, historical prices, technical indicators, or RMSE, and it can preserve uncertainty around those observations. It cannot supply the missing target definition, forecast horizon, preprocessing policy, sequence construction, model layers, loss, optimizer, or metric equation. Faithful reproduction at this stage means reproducing the evidence-coding workflow, not inventing the predictive experiments that the paper itself does not specify.
Source-qualified synthesis for RQ1, RQ2, and RQ3
How can the implementation answer “which methods, inputs, and metrics appear in the literature?” without turning a review of reviews into a misleading benchmark? The key is to perform descriptive aggregation while preserving provenance. The package records which retained review reported SVM, LSTM, historical prices, technical indicators, accuracy, MSE, RMSE, MAPE, or MAE. It does not calculate a new performance score, rank models statistically, or claim that one method wins.
Three review questions become three evidence dimensions
Paper fact. The paper organizes its synthesis around three questions: commonly reported prediction methods, commonly reported information sources, and commonly reported performance metrics. In the implementation, these become the EvidenceDimension.METHOD, EvidenceDimension.INFORMATION_SOURCE, and EvidenceDimension.METRIC categories.
Implementation decision. ReviewCatalog is the central object. It links retained ReviewRecord objects with CodedEvidence, source-qualified EvidenceClaim objects, and explicit warnings. The catalog is a literature evidence store, not a stock-price dataset. Its records contain no model weights, predictions, tensors, or training state.
The catalog offers evidence_for_review(review_id, dimension) for focused inspection. Passing only a review identifier returns all coded observations for that review; passing an EvidenceDimension narrows the result. add_warning records an interpretation problem such as unresolved Table 5 alignment. validate checks cross-record references and refuses to infer missing identities or repair fragmented evidence.
A useful mental model is a filing cabinet. ReviewCatalog.reviews contains the folders, evidence contains labeled observations, and claims contains statements that may have denominators or subset identifiers. The cabinet can be summarized, but its contents should not be detached from their labels and source locations.
Descriptive summaries are not prevalence estimates
DimensionSummary is deliberately modest. It stores canonical labels, the review identifiers in which each label appears, denominator metadata, and uncertainty counts. It does not calculate prevalence, effect sizes, rankings, confidence intervals, or model performance.
The implementation of summarize_dimension makes that boundary explicit:
def summarize_dimension(
catalog: ReviewCatalog, dimension: EvidenceDimension
) -> DimensionSummary:
"""Group coded evidence by label without making prevalence claims.
The output preserves review identifiers and denominator values separately
for every canonical label. In particular, observations from different
review subsets are not merged into a single count or percentage.
"""For example, summarize_dimension(catalog, EvidenceDimension.METHOD) groups method evidence for RQ1. The corresponding call with EvidenceDimension.INFORMATION_SOURCE answers RQ2, and the call with EvidenceDimension.METRIC answers RQ3. The returned labels might include SVM, LSTM, or ANN for methods; historical prices or technical indicators for information sources; and accuracy, MSE, RMSE, MAPE, or MAE for metrics. Their presence means that the coded reviews reported those categories. It does not mean that this package evaluated them.
summarize_by_review serves a different purpose. Rather than grouping globally by label, it returns a mapping from each review identifier to the labels observed in that review for one dimension. This is useful when the question is “which review reported this category?” It remains a review-level presence map: it does not count primary studies or combine denominators.
Keep claims and denominators attached
A coded label is not always enough. A statement such as “LSTM was preferred in 58%” has a meaning that depends on its originating subset and denominator. The generated EvidenceClaim model therefore preserves claim_id, text, subset_id, denominator, and provenance.
add_claim validates and appends one claim without aggregation. validate_claim requires a positive integer denominator when a claim contains an explicit percentage, unless the text explicitly says that the denominator or percentage is unavailable. This is a contract check, not a formula implementation: no percentage is recomputed.
The denominator audit turns those fields into an explicit audit record. Its record shape is represented by the generated DenominatorEntry class:
@dataclass(frozen=True)
class DenominatorEntry:
"""Source-qualified denominator metadata for one quantitative claim.
The denominator is deliberately kept alongside the claim and its subset.
Counts reported for different review subsets are not interchangeable, even
when they describe related methods, inputs, or metrics.
"""
claim_id: str
subset_id: str
denominator: int
source_location: SourceLocation | Noneaudit_denominators(claims) emits one DenominatorEntry for every claim with a supplied denominator. Thus an entry retains all three essential identifiers: claim_id, subset_id, and denominator, along with the source location. A missing denominator remains missing; the audit does not guess one.
This matters because the paper reports quantities from different scopes, including subsets of 12, 30, 34, 45, 57, 122, and 379 studies. Those values are not interchangeable. The abstract-level count of more than 379 primary studies is also not a universal denominator for every percentage reported elsewhere in the review.
Refuse cross-subset aggregation
The package makes the safe behavior explicit through reject_cross_subset_aggregation. It validates the supplied claims, collects their subset_id values, and raises ValidationError when more than one subset is present. The function’s purpose is preventative: it stops a caller from producing a combined statistic that the paper did not report.
This is an implementation decision derived from the paper’s ambiguities, not a new statistical method. A future meta-analysis could define compatible inclusion rules, effect sizes, weighting, and uncertainty estimates, but those choices are absent from the supplied source and are outside this reproduction.
Preserve conflicting findings instead of choosing a winner
The paper contains claims that cannot safely be treated as universal conclusions about deep learning. Some passages report that deep learning is generally more accurate than traditional machine learning, while another discussion says that deep-learning models have not generally outperformed standard models. The implementation must retain such claims with their scopes rather than silently resolving the disagreement.
preserve_conflicting_claims validates each claim and returns it without deduplicating by wording, denominator, or topic. It does not select a winner, assign a weight, or infer that one claim is more credible. This allows two statements about LSTM—or about deep learning more broadly—to coexist when they originate from different review subsets or contexts.
Construct the synthesis report
SynthesisReport holds three DimensionSummary objects: rq1_methods, rq2_information_sources, and rq3_metrics. It also contains qualitative findings, denominator-audit entries, and warnings. The report explicitly does not contain predictions, losses, formulas, or newly measured model-comparison results.
The main method assembles those parts in a fixed order. The following excerpt shows the central mapping from the three research questions to the three evidence dimensions:
# RQ1: which AI methods and technologies are reported?
rq1_methods = summarize_dimension(catalog, EvidenceDimension.METHOD)
# RQ2: which informational sources are reported?
rq2_information_sources = summarize_dimension(
catalog, EvidenceDimension.INFORMATION_SOURCE
)
# RQ3: which evaluation metrics are reported?
rq3_metrics = summarize_dimension(catalog, EvidenceDimension.METRIC)
preserved_claims = preserve_conflicting_claims(catalog.claims)
denominator_entries = audit_denominators(preserved_claims)A caller requests the completed object with synthesize_review(catalog). Conceptually, the result can then be inspected as report.rq1_methods, report.rq2_information_sources, report.rq3_metrics, and report.denominator_audit. The implementation first validates the catalog, preserves conflicting claims, and audits denominators before constructing the report.
The report’s qualitative section uses summarize_limitations. In the generated implementation, available EvidenceClaim text is surfaced with subset and source information. This keeps recommendations—such as combining multimodal data, improving interpretability, examining hyperparameters, or testing across markets—distinct from measured predictive results.
Worked example: two LSTM claims, two scopes
Suppose an external extraction process supplies two claims about LSTM. One claim belongs to a 12-study subset and another belongs to a separate 57-study subset. The claims may mention different review findings, and they may even appear to disagree. The correct procedure is:
Create each claim with a distinct
subset_id.Store the original denominator, 12 or 57, on the corresponding claim.
Attach provenance to both claims.
Add them to the catalog without combining them.
Call
synthesize_review(catalog)and inspect the resulting denominator audit.
The audit should conceptually contain two separate entries, one with subset_id for the 12-study scope and one with subset_id for the 57-study scope. It should not contain a new denominator of 69, 379, or any other combined value. The report therefore communicates “these claims were reported in these scopes,” not “this is the overall percentage for LSTM.”
Likewise, if coded evidence records mention both RMSE and MAE, summarize_dimension catalogs the metric names. It does not reconstruct their mathematical definitions or calculate either metric. The supplied paper contains no canonical equations, so formulas are intentionally absent from this section and from the planned synthesis implementation.
Keep reported workflow facts separate
The constants module stores the paper’s reported workflow facts without treating them as synthesis denominators. REPORTED_FLOW_COUNTS contains the stage-specific values for retrieval, reviews read, abstract screening remainder, post-duplicate handling, and final retention. These values describe the review workflow; they do not count methods or establish the prevalence of a predictive algorithm.
Similarly, the source-specific retrieval counts for Scopus and Web of Science remain separate. Keeping those mappings distinct prevents a database-level retrieval total from being confused with the number of primary studies covered by the retained reviews or with a denominator attached to an LSTM claim.
Boundary of interpretation
The synthesis can faithfully report that SVM, LSTM, and neural-network categories are prominent in the reviewed literature, that historical closing-price time series and technical indicators are common information sources, and that accuracy, MSE, RMSE, MAPE, and MAE are frequently discussed metrics. These are paper-level observations preserved by the catalog.
They are not guarantees that SVM or LSTM performs best, not evidence that a particular feature combination improves forecasting, and not results generated by this Python package. Any claim about predictive performance would require a separate, externally supplied model specification, dataset, target, split, preprocessing policy, and evaluation procedure.
The implementation therefore reproduces the evidence-synthesis method—source-qualified grouping, denominator auditing, and conflict preservation—rather than inventing a meta-analysis or a stock-prediction runtime.
The explicit contract for underspecified predictive reproductions
What should happen when someone wants to turn this review into a stock-prediction model, but the paper does not define the model? The safe answer is to stop before silently choosing one. A reproduction contract acts as a checklist: it records every decision an external predictive study must provide and reports omissions explicitly.
Why a contract is necessary
Paper fact. The source discusses classification and regression, historical prices, technical indicators, text, sentiment, macroeconomic information, and many model families, including SVM, LSTM, ANN, CNN, and ARIMA. However, it does not define a target variable, forecast horizon, label threshold, sequence length, data split, normalization policy, architecture, hyperparameters, optimizer, loss, or training schedule. It also supplies no canonical equations.
Implementation decision. The generated package therefore implements predictive_reproduction_contract, not a stock forecaster. Its responsibility is to make omitted decisions visible before model code is written. It does not instantiate or train an SVM, LSTM, ANN, CNN, ARIMA, or any other predictor, and it does not calculate accuracy, MSE, RMSE, MAPE, MAE, NMSE, correlation coefficient R, or POCID.
This boundary is important because a plausible default is still an unsupported assumption. For example, the paper’s observations about daily data or approximately 1000-day periods describe reviewed studies; they do not establish a universal lookback window. Likewise, the prominence of LSTM in some review findings does not authorize selecting an LSTM architecture for reproduction.
Describe inputs without pretending to implement preprocessing
ModalitySpecification describes one externally defined input source. Its fields are name, shape_description, dtype, frequency, alignment, and leakage_policy. These are contract descriptions rather than loaded array shapes. A numerical time series might therefore be described as (time, features) without inventing a feature count or sequence length.
The generated implementation validates that these descriptions are non-empty strings:
@dataclass
class ModalitySpecification:
"""Describe one externally defined input modality.
``shape_description`` is intentionally textual. For example,
``"(time, features)"`` describes a numerical series without asserting a
sequence length or feature count. ``leakage_policy`` must state how
information leakage is prevented; the paper does not define that policy.
"""
name: str
shape_description: str
dtype: str
frequency: str
alignment: str
leakage_policy: strHere, a multimodal input combines different information types, such as numerical prices and timestamped news. Alignment specifies how observations from those sources correspond, for example through a timestamp or another key. Leakage prevention specifies how information unavailable at prediction time is excluded. The paper recommends combinations of information sources but does not define either procedure.
validate_modalities checks the declarations without loading, reshaping, resampling, or scaling data. validate_temporal_alignment adds a guard for multimodal configurations: declarations must either share an alignment description or explicitly describe an alignment or join procedure. This is a validation rule introduced by the implementation to prevent an ambiguous input contract; it is not a preprocessing algorithm claimed by the paper.
Record the decisions a model would require
PredictiveConfiguration separates task type from the other predictive choices. Its fields are:
task_type, which must be externally identified as classification or regression;target_definition, describing what value or label is predicted;horizon, identifying how far ahead the prediction is made;modalities, containing the declared inputs;lookback, describing sequence construction or historical context;split_policyandscaling_policy, describing data partitioning and normalization;model_familyandhyperparameters, describing the selected model externally;optimizerandloss, describing training choices; andmetrics, naming the evaluation measures to report.
The class uses None to mean that a decision was not supplied. It does not replace missing values with defaults. This distinction is especially useful for intermediate Python users: an omitted field is not the same as a field whose value happens to be zero, empty, or unknown.
The following is an intentionally incomplete configuration. It declares one numerical modality but leaves the target, horizon, and other decisions unspecified:
from review_synthesis.predictive_contract import (
ModalitySpecification,
PredictiveConfiguration,
missing_predictive_fields,
)
numerical = ModalitySpecification(
name="numerical_time_series",
shape_description="(time, features)",
dtype="float32",
frequency="daily",
alignment="timestamp",
leakage_policy="external time-aware policy",
)
config = PredictiveConfiguration(
modalities=[numerical],
)
missing = missing_predictive_fields(config)The descriptive shape (time, features) does not imply a particular tensor rank beyond the text supplied by the external specification, and it does not imply a lookback length. Similarly, frequency="daily" records an intended external description; it does not cause the package to retrieve or resample daily data.
Turn omissions into an actionable report
missing_predictive_fields returns stable field names for decisions that are absent. For the configuration above, the conceptual result includes fields such as task_type, target_definition, horizon, lookback, split_policy, scaling_policy, model_family, hyperparameters, optimizer, loss, and metrics. The implementation does not infer any of them from the review.
build_missing_specification_report groups those omissions into categories such as task_and_target, temporal_construction, data_preprocessing, model_and_optimization, and evaluation. explain_missing_fields adds plain-language explanations. For instance, it explains that the target must include its value or label construction, while the horizon must include its units. These explanations are derived implementation guidance, not additional paper facts.
contract_report packages the result into a JSON-safe dictionary. Its output distinguishes whether the configuration is validated, which fields are missing, which are supplied, and what the implementation boundary is. The generated function explicitly records that no predictive runtime, equations, or training procedure is supplied by the paper.
A strict caller can use assert_faithful_reproduction_possible as a gate:
def assert_faithful_reproduction_possible(config: PredictiveConfiguration) -> None:
"""Reject a predictive contract that still depends on unspecified external decisions."""
if not isinstance(config, PredictiveConfiguration):
raise ValidationError("config must be a PredictiveConfiguration")
validate_modalities(config.modalities)
validate_temporal_alignment(config.modalities)
missing = missing_predictive_fields(config)
if missing:
details = "; ".join(_explanation_for_field(field) for field in missing)
raise IncompleteSpecificationError(
"faithful predictive reproduction is incomplete; missing fields: "
+ ", ".join(missing)
+ ". "
+ details
)IncompleteSpecificationError is therefore an explicit contract response. It does not mean that model training failed; no model-training operation is defined here. It means that the supplied external specification is insufficient for a faithful predictive reproduction.
What this boundary does—and does not—guarantee
The contract validator can ensure that required declarations exist and that modality descriptions are structurally coherent. It cannot determine whether a chosen target is scientifically appropriate, whether a split truly prevents leakage, or whether a selected architecture is effective. Those decisions belong to the external predictive study and must be documented there.
This model-family-agnostic design is deliberate. The paper names incompatible families and does not provide a unified interface for combining them. A future reproduction could supply an SVM with tabular features, an LSTM with sequences, or a multimodal architecture, but each would require details absent from this source. The contract records those details once they are supplied; it does not invent them.
Worked example: stopping before accidental invention
Suppose an external user supplies only the numerical modality shown above. missing_predictive_fields identifies the absent target and horizon along with the rest of the required decisions. contract_report can present those omissions by category, and explain_missing_fields can state why each one matters. A caller may then choose to raise IncompleteSpecificationError through assert_faithful_reproduction_possible rather than proceeding.
The important outcome is not a prediction. It is an auditable boundary: no sequence length, normalization rule, loss, optimizer, or LSTM architecture has been smuggled into the reproduction. Multimodal inputs likewise remain declarations until an external specification defines their alignment and leakage policy.
The planned audit and verification layers were not run under the authoritative policy, so this tutorial makes no claim that the code was executed or that the contract path passed verification. Faithfully reproducing this paper means preserving its review evidence and explicitly exposing its missing predictive details; implementing a predictive model requires a separate, external specification.
Serialization, reporting, and deterministic example data
How do you turn a provenance-rich review catalog into an artifact that another person can inspect without losing uncertainty, dates, or denominators? Use three deliberately separate layers: serialization converts domain objects into JSON-safe data, reporting formats that data for readers, and local command-line tools connect the workflow without pretending to query bibliographic databases or train a stock predictor.
Preserve meaning when converting objects to JSON
Implementation decision. The generated serialization.py module treats serialization as a loss-avoidance step, not as a place to calculate new evidence. record_to_dict converts one supported dataclass, while catalog_to_dict and synthesis_to_dict serialize the two main review artifacts. write_json then writes a deterministic UTF-8 document to a local Path.
The important conversions are explicit:
Enum members such as
Database.SCOPUSare written using their stable string values.dateanddatetimevalues are written as ISO-8601 strings.Noneremains JSONnull, which is essential for an explicitly unspecified included-study count.Tuples and other supported sequences become JSON arrays while preserving order.
Provenance, confidence, subset identifiers, and denominators are ordinary serialized fields rather than comments that could be lost.
The focused implementation excerpt below shows the public catalog boundary. Notice that catalog_to_dict validates the catalog before converting it, but does not repair or infer missing values.
def catalog_to_dict(catalog: ReviewCatalog) -> dict[str, object]:
"""Serialize a review catalog while retaining evidence provenance."""
if not isinstance(catalog, ReviewCatalog):
raise ValidationError("catalog must be a ReviewCatalog value")
catalog.validate()
converted = _to_json_value(catalog, "catalog")
if not isinstance(converted, dict):
raise ValidationError("serialized catalog must be a dictionary")
return converted
def synthesis_to_dict(report: SynthesisReport) -> dict[str, object]:
"""Serialize a source-qualified synthesis report without recomputation."""
if not isinstance(report, SynthesisReport):
raise ValidationError("report must be a SynthesisReport value")
converted = _to_json_value(report, "synthesis_report")
if not isinstance(converted, dict):
raise ValidationError("serialized synthesis report must be a dictionary")
return convertedThis boundary matters for the paper because evidence is source-qualified. A serialized RMSE label must remain a reported metric name, not become a computed value. Likewise, a null field must remain visibly absent rather than being replaced by a guessed count or parameter.
Use schemas to describe, not invent, data
The generated schema.py module provides two JSON-schema-like descriptions. catalog_schema() describes persisted review records, coded evidence, claims, and warnings. predictive_configuration_schema() describes the fields an external predictive reproduction must supply, including task type, target, horizon, modalities, split, scaling, model family, hyperparameters, optimizer, loss, and metrics.
The distinction is important: the predictive schema is a contract for missing information, not a model specification. It does not provide a default LSTM, sequence length, normalization rule, or loss. validate_payload_shape checks required keys and structural types without coercing values or filling gaps.
For example, the catalog schema explicitly permits an included-study count to be an integer or null:
"included_study_count": {"type": ["integer", "null"], "minimum": 0},That null is meaningful. The paper reports ten retained reviews with the sequence 27, unspecified, 53, 12, 34, 122, 24, 57, 30, and 20. The implementation must preserve the unspecified entry instead of estimating it from the other values.
Render evidence without changing it
Serialization is for machines; reporting is for readers. The generated reporting.py module offers render_summary, render_markdown_report, and render_warning_section. These functions format existing objects and make important boundaries visible:
render_markdown_reportlabels the result as a review-evidence synthesis.Flow counts remain stage-specific rather than being presented as one total.
Each dimension summary displays source review identifiers, reported denominators, and uncertainty counts.
The denominator audit displays each claim’s
claim_id,subset_id, denominator, and source location.Warnings are rendered as a visible section rather than being hidden in program logs.
The report renderer itself states the scope clearly:
"## Scope and implementation boundary",
"",
"This report represents a systematic review of systematic reviews. Its entries are reported literature evidence, not primary stock-price datasets and not results from a newly trained model.",
"",
"The rendering preserves coded labels, source-review identifiers, uncertainty counts, and denominator metadata. It does not reconstruct equations, calculate metric formulas, combine incompatible study subsets, or report predictive performance.",This is presentation behavior, not new analysis. For instance, a report may show that a review catalog contains LSTM or MAE, but it does not claim that the package trained an LSTM or evaluated predictions with MAE.
Keep the command-line path local
The generated cli.py module provides a local interface with two input modes: deterministic synthetic examples or a user-supplied local JSON file. build_parser exposes --example, --input, --output, and --format options. load_local_records parses local candidates and extraction batches; it does not contact Scopus, Web of Science, Rayyan, Yahoo Finance, or another external service.
The following invocation is documentation for the available interface, not a claim that it was run here:
python -m review_synthesis.cli --example --output report.jsonWith --format json, the CLI can include the serialized catalog, flow counts, synthesis report, and missing-specification report. With --format markdown, it uses render_markdown_report; with --format text, it uses render_summary. The generated output should be understood as a local workflow artifact, not as a database export or a predictive experiment.
Worked example: synthetic records to a labeled artifact
The example flow begins with example_candidates() and example_extraction_batches() from src/review_synthesis/example_data.py. These functions intentionally create small, deterministic fixtures. The candidates include records attributed to both databases and a repeated title so that provenance and deduplication can be demonstrated. The extraction batch contains separate review-level fields for methods, information sources, and metrics. example_claims() supplies illustrative claims with a denominator, but its text is explicitly synthetic.
The conceptual pipeline is:
Create local candidates and extraction batches.
Supply screening decisions to
run_review_pipeline.Obtain a
PipelineResultcontaining the catalog, screening log, flow counts, synthesis report, and missing-specification report.Pass the catalog to
catalog_to_dictand the synthesis report tosynthesis_to_dict.Pass the report and flow counts to
render_markdown_report.Write the combined artifact with
write_json.
The dedicated scripts/build_example_report.py script follows that path and labels its output with artifact_type: "synthetic_review_synthesis_example". Its explanatory metadata also states that the artifact is not the paper’s bibliographic dataset and contains no trained-model results. This labeling is necessary because deterministic data is useful for teaching the interfaces, but it is not evidence that the paper’s original records were recovered.
A second script, scripts/export_reported_facts.py, serves a different purpose. It exports supplied paper facts such as the article identifier, search metadata, source-specific retrieval counts, stage-specific flow counts, and the ten included-study counts. The unspecified review count is preserved as JSON null, and the export includes the supplied ambiguity and warning lists. It is therefore a facts manifest, not a reconstruction of the original database contents.
What this artifact boundary guarantees—and what it does not
The serialization and reporting layer guarantees only the behavior represented by its contracts: supported objects can be converted without dropping explicit nulls, provenance, confidence, ordering, or denominator metadata; reports can expose those fields; and local examples can be labeled as synthetic. The paper itself supplies no canonical equations, so no equation or metric formula appears in these artifacts.
The implementation also does not claim that the paper prescribes a Python dependency stack. The source mentions tools including Python, TensorFlow, NumPy, Pandas, Keras, Scikit-Learn, TA-Lib, TA4J, and MATLAB, but the generated local interfaces use the package’s own records and standard local file handling rather than treating any named tool as mandatory.
No code execution, test generation, static verification, semantic code verification, tutorial-section verification, or final quality review was performed under the authoritative run policy. The example commands and artifact flow above explain the intended interfaces only. Faithful reproduction of this paper ends with preserving its review evidence and limitations; a stock-prediction runtime would require a separate external specification.
Static and semantic verification strategy—and what was not performed
How can you tell whether this reproduction preserves the paper’s meaning without accidentally claiming that a stock-prediction model exists? Treat verification as a set of explicit invariants, not as a performance test. The relevant promises are that records retain provenance, exclusions retain reasons, reported counts remain separated, uncertain evidence remains uncertain, and generated reports do not claim newly trained models.
This section describes the intended verification strategy and the generated audit hooks. It also states the actual status precisely: verification was skipped under the authoritative run policy. No code execution, test generation, semantic code verification, tutorial-section verification, or final quality review occurred.
What static verification would inspect
Implementation decision. Static verification would inspect the Python package without running the research workflow. For this project, that means parsing each planned Python file with the Python abstract syntax tree, checking imports and public symbols, inspecting dataclass fields and annotations, and comparing public signatures with the planned interfaces.
The same review would inspect the JSON-like structures returned by catalog_schema() and predictive_configuration_schema(). The catalog schema is intended to require review records, coded evidence, claims, and warnings while preserving nullable fields. In particular, None must remain available for an explicitly unspecified study count or denominator rather than being replaced with an estimate. The predictive schema documents required external decisions but does not provide defaults for a target, horizon, architecture, or optimizer.
The generated schema.py module exposes structural validation through validate_payload_shape. Its responsibility is deliberately narrow: check required keys and supported types without coercing missing information.
def validate_payload_shape(payload: Mapping[str, object], schema: Mapping[str, object]) -> None:
"""Validate required keys and supported structural types without coercion."""
if not isinstance(payload, Mapping):
raise ValidationError("payload must be a mapping")
if not isinstance(schema, Mapping):
raise ValidationError("schema must be a mapping")
_validate_node(payload, schema, "payload")Readers should notice the phrase “without coercion.” A structural validator may reject an invalid payload, but it must not repair a missing provenance field, infer a denominator, or turn an uncertain Table 5 association into a reported fact. Those are evidence decisions, not harmless formatting operations.
A future enabled static pass would also check that run_review_pipeline depends on the intended local modules and that PipelineResult contains review artifacts rather than predictions, model weights, losses, or newly computed scores. The orchestration boundary is important: the function consumes externally supplied records and decisions, while live database retrieval and model fitting remain outside this paper-faithful implementation.
What semantic verification would inspect
Derived explanation. Semantic verification asks whether the code’s behavior matches the paper’s scope and the implementation contracts. It is more than checking that names and types look plausible. It would inspect whether every extracted item has a source review and location, whether each exclusion has a screening stage and reason, whether counts remain tied to their workflow stages, and whether claims preserve their original subset and denominator.
The generated audit.py module provides three focused audit entry points:
audit_cataloginspects cross-record references, provenance, uncertainty, denominator preservation, flow counts, and report scope.audit_reported_countscompares supplied flow values with the paper’s reported stage-specific values without modifying them.audit_no_model_claimssearches generated qualitative text and warnings for unsupported language suggesting that a model was trained or evaluated.
The last check is a safeguard against scope drift. It is not evidence that a model exists or that the audit found a valid model implementation. Its purpose is to catch wording that would contradict the paper-faithful boundary.
def audit_no_model_claims(report: SynthesisReport) -> list[AuditFinding]:
"""Detect unsupported claims that this package trained or evaluated a model."""
if not isinstance(report, SynthesisReport):
return [
_finding(
"error",
"report_type",
"report must be a SynthesisReport instance",
)
]
text_parts: list[str] = []
text_parts.extend(report.qualitative_findings)
text_parts.extend(report.warnings)
text = "\n".join(text_parts).casefold()
unsupported_phrases = (
"trained a model",
"model was trained",
"achieved accuracy",
"predicted prices",
"generated predictions",
"fitted model",
"test-set performance",
"validation score",
)This audit is especially relevant here because the paper names SVM, LSTM, neural networks, ARIMA, and many other methods. A report that says one of these methods achieved a result would exceed the supplied evidence unless that statement were explicitly attributed to a reviewed study and retained with its source context.
A hypothetical audit walkthrough
Consider a catalog containing one retained review and one uncertain SVM observation extracted from fragmented Table 5 material. A semantic audit would conceptually follow this checklist:
Confirm that the evidence’s
source_review_idrefers to a retained review or an explicitly external source.Confirm that its
Provenance.source_locationidentifies the relevant table or section.Confirm that its confidence remains
UNCERTAINwhen row alignment was not established.Confirm that any quantitative
EvidenceClaimretains itssubset_idand denominator.Compare the supplied
FlowCountswith the reported values: 69 retrieved, 43 reviews read, 17 remaining after abstract screening, 16 after duplicate or same-author handling, and 10 finally retained.Inspect the synthesis text for unsupported claims about training, predictions, or test-set performance.
The constants used for this comparison are source-qualified facts rather than inferred prevalence estimates. REPORTED_RETRIEVAL_COUNTS keeps Scopus at 40 and Web of Science at 29, while REPORTED_FLOW_COUNTS keeps the PRISMA-style values separate by stage. Neither mapping proves that a supplied local fixture reproduces the original database export.
REPORTED_RETRIEVAL_COUNTS: Final[Mapping[str, int]] = MappingProxyType(
{
"Scopus": 40,
"Web of Science": 29,
}
)If a local example contains different counts, the intended behavior is to report a difference, not to overwrite the local data or claim reconciliation. Similarly, a mismatch does not by itself show that the paper is incorrect; it may simply mean that the local records are synthetic or incomplete.
Actual verification status for this tutorial
Run-policy fact. The authoritative policy disabled local static verification, test generation, code execution, semantic code verification, tutorial-section verification, and final quality review. The supplied verification records therefore have a skipped status. This tutorial does not claim that the generated files parsed successfully, that schemas validated real artifacts, that audits returned no findings, or that any example command ran.
The audit functions are planned safeguards and generated interfaces, not completed verification results. The same distinction applies to PipelineResult: its type describes what run_review_pipeline would return for supplied local inputs, but no execution outcome is asserted here.
Important limitations remain even if these checks are enabled later. Table 5 is fragmented, so some method, source, and metric associations require manual source-PDF review. The paper’s geographic inclusion criterion and some review-subset statements are ambiguous. No canonical equations, target definition, forecast horizon, preprocessing policy, architecture, hyperparameters, loss, optimizer, or training schedule were supplied. The local examples are synthetic and must not be presented as the original Scopus or Web of Science records.
The faithful boundary is therefore concise: reproducing this paper means reproducing its review-selection, provenance, uncertainty, and evidence-synthesis workflow. Any executable stock-prediction model requires a separate external specification, and its correctness cannot be established from this review alone.
Use the button or URL below to download the source code.


