Spaces:
Running
Running
AJAY KASU commited on
Commit ·
e6c4c6c
1
Parent(s): f65f1d3
Fix: Add Directional Signage Logic to Attribution to prevent Negative Rally hallucination
Browse files- ai/prompts.py +6 -1
- analytics/attribution.py +27 -2
ai/prompts.py
CHANGED
|
@@ -49,9 +49,14 @@ Write a "Trailing 30-Day Risk & Performance Attribution" report relative to the
|
|
| 49 |
- DO NOT say "We held [Excluded Stock]".
|
| 50 |
4. **Chain of Thought (Mental Check)**:
|
| 51 |
- First, scan the JSON. Identify the "Status" of the top movers.
|
| 52 |
-
- Second,
|
| 53 |
- Third, write the commentary based ONLY on these facts.
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
Write a professional, concise 3-paragraph commentary.
|
| 56 |
"""
|
| 57 |
|
|
|
|
| 49 |
- DO NOT say "We held [Excluded Stock]".
|
| 50 |
4. **Chain of Thought (Mental Check)**:
|
| 51 |
- First, scan the JSON. Identify the "Status" of the top movers.
|
| 52 |
+
- Second, READ THE "Reasoning" FIELD. This is the ground truth.
|
| 53 |
- Third, write the commentary based ONLY on these facts.
|
| 54 |
|
| 55 |
+
5. **Signage Rules (CRITICAL)**:
|
| 56 |
+
- If Return is NEGATIVE (e.g. -15%), NEVER use the word "rallied". Use "declined", "sold off", or "dropped".
|
| 57 |
+
- If Reasoning says "Protected (Avoided Loss)", say: "The portfolio benefited from avoiding [Stock], which declined by [Return]."
|
| 58 |
+
- If Reasoning says "Drag (Missed Rally)", say: "Performaance was held back by not owning [Stock], which rose by [Return]."
|
| 59 |
+
|
| 60 |
Write a professional, concise 3-paragraph commentary.
|
| 61 |
"""
|
| 62 |
|
analytics/attribution.py
CHANGED
|
@@ -107,12 +107,37 @@ class AttributionEngine:
|
|
| 107 |
def build_truth_table(dataframe, n=5):
|
| 108 |
results = []
|
| 109 |
for ticker, row in dataframe.head(n).iterrows():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
results.append({
|
| 111 |
"Ticker": ticker,
|
| 112 |
"Sector": row['sector'],
|
| 113 |
-
"Status":
|
|
|
|
| 114 |
"Active_Contribution": f"{row['contribution']:.4f}",
|
| 115 |
-
"Return": f"{row['ret']:.2%}"
|
|
|
|
| 116 |
})
|
| 117 |
return results
|
| 118 |
|
|
|
|
| 107 |
def build_truth_table(dataframe, n=5):
|
| 108 |
results = []
|
| 109 |
for ticker, row in dataframe.head(n).iterrows():
|
| 110 |
+
status = get_status(row)
|
| 111 |
+
ret_val = row['ret']
|
| 112 |
+
active_contrib = row['contribution']
|
| 113 |
+
|
| 114 |
+
# Directional Signage Logic (The "Why")
|
| 115 |
+
reasoning = "Neutral"
|
| 116 |
+
|
| 117 |
+
if status == "Excluded":
|
| 118 |
+
if ret_val < 0:
|
| 119 |
+
reasoning = "Protected (Avoided Loss)"
|
| 120 |
+
else:
|
| 121 |
+
reasoning = "Drag (Missed Rally)"
|
| 122 |
+
elif status == "Underweight":
|
| 123 |
+
if ret_val < 0:
|
| 124 |
+
reasoning = "Protected (Underweight Loser)"
|
| 125 |
+
else:
|
| 126 |
+
reasoning = "Drag (Underweight Winner)"
|
| 127 |
+
elif status == "Overweight":
|
| 128 |
+
if ret_val > 0:
|
| 129 |
+
reasoning = "Value Add (Stock Picking)"
|
| 130 |
+
else:
|
| 131 |
+
reasoning = "Detractor (Overweight Loser)"
|
| 132 |
+
|
| 133 |
results.append({
|
| 134 |
"Ticker": ticker,
|
| 135 |
"Sector": row['sector'],
|
| 136 |
+
"Status": status,
|
| 137 |
+
"active_contribution_raw": float(row['contribution']), # Keep raw for sorting helper if needed
|
| 138 |
"Active_Contribution": f"{row['contribution']:.4f}",
|
| 139 |
+
"Return": f"{row['ret']:.2%}",
|
| 140 |
+
"Reasoning": reasoning
|
| 141 |
})
|
| 142 |
return results
|
| 143 |
|