How I Designed TradeMind's Six-Stage Hindsight Learning Loop
A trading system can calculate RSI correctly, generate a plausible thesis, and still repeat the same mistake tomorrow. I built TradeMind around a different question: how can a market-analysis service carry useful experience from one decision into the next without asking an LLM to remember everything or letting it invent the underlying numbers?
TradeMind is a quantitative research and paper-trading system for Indian instruments such as NIFTY 50, BANK NIFTY, Reliance, TCS, and Infosys. Its architecture separates numerical work from language-model reasoning: Python computes market features, tabular models estimate direction and volatility, Groq turns that evidence into a structured hypothesis, and Vectorize Hindsight supplies long-term experiential memory. The interesting part is not any single model. It is the feedback path connecting a market snapshot, a decision, an outcome, and a later decision.
The system in six stages
The workflow is deliberately explicit. It is implemented in backend/services/workflow_loop_service.py and exposes six stages in its response:
- Ingest market data and calculate technical indicators.
- Run tabular machine-learning analysis.
- Recall relevant prior experience from Hindsight.
- Ask Groq to formulate a structured trading hypothesis.
- Execute or evaluate the hypothesis in a paper-trading environment.
- Reflect on the outcome and retain a lesson for future retrieval.
The rest of the codebase supports this path. data_loader.py supplies OHLCV data; technical_indicators.py calculates RSI, moving averages, MACD, Bollinger Bands, and ATR; ml_models_service.py produces tabular analysis; llm_service.py handles hypothesis generation; paper_trading_service.py models virtual execution; and hindsight_service.py wraps the Hindsight client. FastAPI exposes the services, while the React frontend presents the trading dashboard, charts, analysis, and memory views.
I made one boundary non-negotiable: the language model should explain and synthesize evidence, not manufacture it. The README describes this as “No LLM Training on Raw Stock Numbers.” Numerical features and trading calculations stay in Python. The LLM receives those results and produces a typed hypothesis with a direction, confidence, supporting evidence, entry assumptions, stop-loss and target assumptions, invalidation conditions, and risk explanation.
That split matters in a financial workflow. If a response says RSI is 74.2, I want that value to trace back to a deterministic calculation over a known candle series—not to a token prediction. It also means I can test the indicator code independently of the language model.
Hindsight is a memory service, not a chat transcript
I use Hindsight as an experiential memory layer. A transcript records what was said. A useful memory system should help answer a different question: what past experience is relevant to the situation in front of me now?
TradeMind uses Hindsight’s three core operations:
-
retainadds market context, hypotheses, and completed paper-trade outcomes to a memory bank. -
recallsearches for experiences related to the current instrument and market conditions. -
reflectsynthesizes lessons across remembered experiences.
The integration is intentionally concentrated in backend/services/hindsight_service.py, rather than scattered across every feature. The service creates a client from configuration, ensures the configured bank exists, and runs synchronous SDK calls in a small thread pool. That thread boundary is there to keep the synchronous client work away from the request’s event-loop execution path.
The relevant part of the recall path is small:
res = self._execute_sync(
self.client.recall,
bank_id=self.bank_id,
query=query,
tags=tags,
budget="mid"
)
The query is not just “What happened to NIFTY?” It includes the current instrument and the tabular model’s view of direction, volatility regime, and sentiment. This gives the retrieval system a description of the decision context rather than only a ticker symbol.
The service then converts returned items into MemoryItem objects containing content, tags, score, and bank identity. The caller can pass those memories to the reasoning layer. This is where the Hindsight GitHub project and the Hindsight documentation become useful references: retain, recall, and reflect are different operations in a memory lifecycle, not interchangeable names for saving and loading chat history. The broader idea is also explained in Vectorize’s overview of agent memory for stateful AI systems.
Running retain and recall from the terminal
I also wanted a test that exercises the same service boundary the application uses, rather than a mocked memory object. From the repository root, with the Hindsight service configured in .env, I can run this smoke test:
python - <<'PY'
import json
import time
import uuid
from backend.services.hindsight_service import hindsight_service
marker = f"TRADEMIND-HINDSIGHT-SMOKE-{uuid.uuid4().hex[:10]}"
content = (
f"{marker}: A test memory for NIFTY. "
"This is a connectivity check, not a trading recommendation."
)
# 1. Write a uniquely identifiable memory to the configured bank.
retained = hindsight_service.retain_memory(
content=content,
tags=["NIFTY", "smoke_test"],
metadata={"test_marker": marker, "purpose": "retain_recall_smoke_test"},
)
print("RETAIN:", json.dumps(retained, indent=2))
if not retained.get("success"):
raise SystemExit("Hindsight retain failed; check service URL, credentials and bank configuration.")
# 2. Query for that exact marker. Retain may be indexed asynchronously,
# so poll briefly instead of assuming it is immediately searchable.
memories = []
for attempt in range(15):
memories = hindsight_service.recall_memories(
query=f"Find the TradeMind smoke-test memory containing {marker}",
tags=["smoke_test"],
limit=5,
)
if any(marker in item.content for item in memories):
break
time.sleep(2)
print("RECALL:")
for item in memories:
print(json.dumps({
"bank_id": item.bank_id,
"content": item.content,
"tags": item.tags,
"score": item.score,
}, indent=2, default=str))
if not any(marker in item.content for item in memories):
raise SystemExit(
"Retain succeeded, but the marker was not returned by recall within 30 seconds. "
"Check Hindsight indexing and query behavior before treating this as a pass."
)
print("PASS: the retained marker was returned by Hindsight recall.")
PY
The output is generated by the configured Hindsight instance; I do not hard-code a successful transcript into the article. A successful run prints the actual retain response (including its operation identifier when supplied), the recalled memory content and metadata, and the final PASS line. If retain fails, or recall does not return the marker within the polling window, the script exits with an error instead of presenting a false success. Run it against a development bank because it writes a smoke-test memory to that bank.
The underlying SDK calls in hindsight_service.py are the same calls this test reaches through the application wrapper: self.client.retain(...) and self.client.recall(...). That makes the terminal check useful evidence of connectivity and round-trip retrieval, while still not claiming that the trading workflow has learned a validated strategy from this test.
Retrieval is conditioned on the market state
In the workflow, recall happens after the market snapshot and tabular ML analysis are available. That order is intentional. A generic memory query would retrieve broad trading advice. A state-conditioned query can ask for previous experiences that resemble the current setup.
The workflow constructs that query from the model output:
recall_query = (
f"Trading {instrument_id} when ML predicts {ml_result.direction.direction} "
f"with volatility {ml_result.volatility.volatility_regime} "
f"({ml_result.volatility.predicted_volatility_pct}%) "
f"and sentiment {ml_result.sentiment.sentiment_label}. "
"Previous lessons and outcome reviews."
)
recalled_memories = hindsight_service.recall_memories(
query=recall_query,
limit=3
)
The recalled information is then available to the hypothesis-generation path. llm_service.py also has a memory-aware hypothesis method, so the model can reason over the current indicators and recalled lessons together. The aim is not to force the latest memory to dictate a trade. It is to add relevant precedent to the evidence the model must consider.
For example, imagine the latest NIFTY snapshot has a bullish trend, elevated RSI, and a low-volatility regime. A useful recalled experience might describe a prior overbought setup in which momentum weakened before price reversed. The model should not treat that memory as a universal rule. It should compare it with the current evidence, state whether the precedent applies, and describe what would invalidate the thesis.
That is a better use of memory than simply prepending the last ten chat messages to every prompt. A transcript is chronological; a retrieval system is contextual. The latter can bring an older but relevant experience into a new analysis without making the entire conversation history part of the prompt.
The outcome is what makes the loop interesting
Recall alone does not create a learning loop. If a system remembers only its own hypotheses, it accumulates opinions. It needs outcomes to compare those opinions against.
TradeMind’s intended feedback path captures the instrument, market context, model output, trade assumptions, execution details, and realized result. The Hindsight service also has a dedicated method for reflecting on a completed paper trade:
lesson = self.reflect_lessons(query)
retain_res = self.retain_memory(
content=f"QUALIFIED LESSON ({instrument}): {lesson}",
tags=[instrument, "verified_lesson", outcome_str],
metadata={
"instrument": instrument,
"net_pnl": str(net_pnl),
"outcome": outcome_str,
"learned_at": datetime.now().isoformat()
}
)
This is the point where I want the system to be careful. A profitable trade does not prove that the thesis was sound; it may have benefited from noise. A losing trade does not prove that the reasoning was wrong; execution, sizing, and market regime matter. The reflection request therefore needs the original hypothesis and the actual entry, exit, P&L, and exit reason. The retained lesson should preserve that evidence, not turn one outcome into a universal trading rule.
In a production implementation, the workflow must only reflect after an actual paper-trade outcome exists. If the position is still open or there is not enough forward market data, the state should remain pending. No synthetic exit should be used to manufacture a completed experience. That distinction is essential because Hindsight is only as useful as the provenance of what it stores.
There is an important engineering lesson in the current repository. The workflow code presently derives a fixed expected_move_pct from the model’s predicted direction, calculates a synthetic exit price, and closes the paper order immediately. That is useful as a placeholder for exercising the flow, but it is not evidence of a market outcome. The production version described here replaces that shortcut with subsequent eligible market updates or replay candles, and retains a lesson only after the exit has been evaluated against those real observations. This is not a benchmark claim; it is a correctness requirement for the loop to mean what its name says.
Keep the memory boundary explicit
A central memory service should not become a second database for everything. TradeMind has different persistence needs:
- Conversation history belongs in a transactional store, where ordering, edits, ownership, and pagination are well-defined.
- Paper orders and balances belong in a transactional trading store, where duplicate execution and P&L consistency matter.
- Raw OHLCV candles belong in the market-data layer, with source and timestamp metadata.
- Hindsight should store concise, retrievable experiences and lessons, not raw uploaded files or the complete financial ledger.
This separation also matters for multi-user deployments. The current Hindsight service uses a configured bank identifier in a global service instance. That is adequate for a single configured environment, but a SaaS deployment needs an explicit user or organization memory boundary. A user’s risk preferences and trading history must not be retrievable by another user. The application must enforce ownership checks as well as use appropriately scoped memory banks or the supported Hindsight isolation mechanism.
The system should also degrade honestly. When Hindsight is unavailable or its API key is missing, TradeMind can still calculate indicators and run deterministic parts of the analysis. It should report that memory is offline and omit recalled lessons. It should never invent a memory count, pretend a recall succeeded, or present a generic fallback rule as something Hindsight remembered.
What the user should see
I want the memory contribution to be inspectable. When TradeMind generates a hypothesis, the UI should be able to show which memories were recalled and which ones influenced the answer. That allows a user to challenge a remembered lesson, distinguish a verified outcome from a preference, and understand why a recommendation differs from an earlier one.
A concrete interaction might look like this:
- The user asks for a NIFTY analysis.
- TradeMind loads the latest eligible market snapshot and calculates indicators.
- The tabular models classify direction, volatility, and sentiment.
- Hindsight retrieves a previous experience involving a similar volatility regime and momentum state.
- Groq generates a structured hypothesis, citing the current numerical evidence and acknowledging the relevant precedent.
- The user paper-trades the setup. Once a real simulated exit is recorded, TradeMind reflects on the hypothesis versus the outcome and retains a qualified experience.
- On a later setup, recall can surface that experience if the new context is relevant.
The result is not a model that changes its weights after every conversation. It is a reasoning service whose future context can improve as its evidence base grows. That distinction keeps the system understandable and makes failures easier to investigate.
Lessons I would reuse
1. Calculate numbers outside the LLM. Keep indicators, prices, quantities, fees, and P&L deterministic. Let the model explain the evidence rather than create it.
2. Retrieve after you know the current context. A memory query built from instrument, direction, volatility, and sentiment is more useful than a broad search for the instrument name alone.
3. Store outcomes with provenance. Preserve the hypothesis, timestamps, source, execution assumptions, and exit reason. A label such as WIN is not a lesson by itself.
4. Treat memory availability as a real service state. Offline memory should be visible and should not silently turn into fabricated recall.
5. Measure whether memory helps before claiming that it does. The repository includes a memory-versus-no-memory experiment path, but its current Agent B logic uses hard-coded memory-style rules rather than a true sequential Hindsight retrieval loop. A credible comparison needs to retrieve only information available at each historical decision point, evaluate against later candles, and report the resulting metrics without leaking future information.
That last point is the standard I would use to judge the whole design. Hindsight gives TradeMind a place to retain and retrieve experience. The engineering work is making sure that experience is relevant, correctly scoped, tied to real outcomes, and visible when it affects a decision. The six stages are useful only when the last stage improves the evidence available to the first four on the next cycle.




Top comments (1)
The hindsight loop becomes credible when every stage preserves the decision that was available at the time, not just the eventual outcome. For a trading-oriented system, I would log the original evidence, constraints, action, and counterfactual separately. That prevents the learner from quietly using information that arrived later and calling the result an improvement.