From 0fc285927928342b389621a524e6f3cbe6a3e135 Mon Sep 17 00:00:00 2001 From: Lavisha Kapoor Date: Fri, 18 Apr 2025 01:20:10 +0530 Subject: [PATCH] backtesting improvement --- backend/app/data/sample_data.csv | 87 +--- .../__pycache__/backtest.cpython-312.pyc | Bin 1446 -> 1526 bytes backend/app/routes/backtest.py | 36 +- .../__pycache__/backtester.cpython-312.pyc | Bin 4664 -> 9178 bytes backend/app/services/backtester.py | 404 +++++++++++++++--- .../bs4/__pycache__/__init__.cpython-312.pyc | Bin 39616 -> 39616 bytes .../bs4/__pycache__/element.cpython-312.pyc | Bin 107371 -> 107371 bytes .../bs4/__pycache__/filter.cpython-312.pyc | Bin 28605 -> 28605 bytes .../builder/__pycache__/_lxml.cpython-312.pyc | Bin 18385 -> 18385 bytes .../__pycache__/__config__.cpython-312.pyc | Bin 5243 -> 5243 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 574 -> 574 bytes .../__pycache__/decoders.cpython-312.pyc | Bin 8322 -> 8322 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 1743 -> 1743 bytes .../__pycache__/multipart.cpython-312.pyc | Bin 67784 -> 67784 bytes frontend/src/App.js | 119 +----- frontend/src/components/BlocklyEditor.js | 72 +--- frontend/src/components/StrategySelector.js | 65 --- frontend/src/components/customBlocks.js | 105 ++--- frontend/src/generators/jsonGenerator.js | 106 +---- 19 files changed, 459 insertions(+), 535 deletions(-) diff --git a/backend/app/data/sample_data.csv b/backend/app/data/sample_data.csv index 5f7a6439..f4076925 100644 --- a/backend/app/data/sample_data.csv +++ b/backend/app/data/sample_data.csv @@ -1,67 +1,24 @@ Date,Close,High,Low,Open,Volume ,AAPL,AAPL,AAPL,AAPL,AAPL -2024-11-11,223.98373413085938,225.45212091614073,221.25673663335022,224.7528927426808,42005600 -2024-11-12,223.98373413085938,225.34224111445195,223.11469448970428,224.30339000560164,40398300 -2024-11-13,224.8727569580078,226.4010754152989,222.5153482196322,223.76397540644066,48566200 -2024-11-14,227.9693603515625,228.61864039722624,224.7528954725484,224.77287777547028,44923900 -2024-11-15,224.7528839111328,226.67077335813082,224.023689933323,226.15134020310103,47923700 -2024-11-18,227.7695770263672,229.4876892234719,224.92270099550518,225.00261496304853,44686000 -2024-11-19,228.02928161621094,229.90722171238716,226.41106572007726,226.73070634208972,36211800 -2024-11-20,228.7484893798828,229.67746064503265,225.64190448211673,227.80951934283004,35169600 -2024-11-21,228.26902770996094,229.90722593821596,225.46211628612434,228.6286329426038,42108300 -2024-11-22,229.61753845214844,230.46661102927646,227.80952873867182,227.80952873867182,38168300 -2024-11-25,232.61424255371094,232.99383009111634,229.48769048234254,231.2058026888721,90152800 -2024-11-26,234.8018341064453,235.31128373473052,233.07373841155265,233.07373841155265,45986200 -2024-11-27,234.67198181152344,235.43115689965902,233.55321672420507,234.21249554010248,33498400 -2024-11-29,237.06935119628906,237.5488197625333,233.71304074851764,234.55211454995276,28481400 -2024-12-02,239.3268585205078,240.52553753151284,236.89953466637493,237.00941446483552,48137100 -2024-12-03,242.3834991455078,242.49337894569658,238.6376176509325,239.54662188503684,38861000 -2024-12-04,242.74310302734375,243.84190102311462,240.9850414761411,242.60325739533934,44383900 -2024-12-05,242.7730712890625,244.2714238935446,241.86408229295347,243.72204013219255,40033900 -2024-12-06,242.5732879638672,244.3613305629988,241.8141281502541,242.6432183998472,36870600 -2024-12-09,246.47900390625,246.96847124510987,241.48449521514058,241.56440918324205,44649200 -2024-12-10,247.4978790283203,247.93739821602466,245.0705399660552,246.61884065291167,36914800 -2024-12-11,246.2192840576172,250.52454792031958,245.98952569372008,247.68767076725757,45205800 -2024-12-12,247.68768310546875,248.46682524629017,245.41017310687806,246.61885092340057,32777500 -2024-12-13,247.85748291015625,249.01619729452798,245.96955931284188,247.54782582286595,33155300 -2024-12-16,250.7642822265625,251.10392039685559,247.3780059899919,247.71764416028498,51694800 -2024-12-17,253.20159912109375,253.5512208135588,249.50566587259755,249.80533943156328,51356400 -2024-12-18,247.77757263183594,254.0007260278641,247.46791553993853,251.88305927654196,56774100 -2024-12-19,249.51565551757812,251.72323503929724,246.81862390484895,247.22817727073837,60882300 -2024-12-20,254.21051025390625,254.71994465608725,245.42017186050566,247.76758181309805,147495300 -2024-12-23,254.98965454101562,255.36922684225433,253.17164603591402,254.49020366493687,40858800 -2024-12-24,257.91644287109375,257.92641115938613,255.0096198916901,255.2094124344067,23234700 -2024-12-26,258.7355041503906,259.81433504408284,257.34704665632455,257.90642916266114,27237100 -2024-12-27,255.30929565429688,258.41589596104086,252.78207543596488,257.54682582842287,42355300 -2024-12-30,251.9230194091797,253.22159473829512,250.47461491371794,251.95298524244893,35557500 -2024-12-31,250.1449737548828,253.00183336212825,249.15605553782555,252.16275955239965,39480700 -2025-01-02,243.5821990966797,248.826433311358,241.55442975303322,248.65660659999048,55740700 -2025-01-03,243.0927276611328,243.91181916074598,241.624340886356,243.0927276611328,40244100 -2025-01-06,244.73092651367188,247.05836939894766,242.93290033169586,244.04168187374515,45045600 -2025-01-07,241.94400024414062,245.28032844029428,241.08494412707995,242.71314361950866,40856000 -2025-01-08,242.43344116210938,243.44234164075266,239.78635773622742,241.65429905026372,37628900 -2025-01-10,236.58987426757812,239.8962364632009,232.74409661721964,239.7463920626497,61710900 -2025-01-13,234.14256286621094,234.41227060505477,229.46771001708314,233.27352322575203,49630700 -2025-01-14,233.02378845214844,235.8606656353981,232.21468051006943,234.49217517741417,39435300 -2025-01-15,237.60874938964844,238.6975638581554,234.17252500331003,234.38230107288462,39832000 -2025-01-16,228.00930786132812,237.74859991927732,227.7795647267042,237.089336348529,71759100 -2025-01-17,229.7274169921875,232.03487756376998,228.22906438809179,231.86506609768284,68488300 -2025-01-21,222.39547729492188,224.17352112887505,219.13906319002535,223.75398423747347,98070400 -2025-01-22,223.58416748046875,223.87384226456615,219.54859611049605,219.54859611049605,64126500 -2025-01-23,223.41436767578125,226.78066167492807,222.05586069453574,224.493183388126,60234800 -2025-01-24,222.5353240966797,225.38220009721186,221.16683361690275,224.53312753788666,54697900 -2025-01-27,229.6075439453125,231.89502211760887,223.73399710835045,223.77396171163917,94863400 -2025-01-28,237.99832153320312,239.9262098046868,230.55650666713984,230.59647127201654,75707600 -2025-01-29,239.0971221923828,239.5965730646729,233.75299176206667,233.86287156365174,45486100 -2025-01-30,237.3290557861328,240.52553823889048,236.94948349745044,238.40787147181928,55658300 -2025-01-31,235.74081420898438,246.91852728753366,233.18362815459494,246.91852728753366,101075100 -2025-02-03,227.75958251953125,231.5753945205246,225.45212191586603,229.73741895869952,73063300 -2025-02-04,232.54432678222656,232.87396618357337,226.40107197135944,227.00041910872287,45067300 -2025-02-05,232.21469116210938,232.4144684638066,228.0193068583431,228.27901582634638,39620300 -2025-02-06,232.9638671875,233.54323203112875,230.17692277145878,231.0359788855247,29925300 -2025-02-07,227.3800048828125,233.74300400322835,227.0104008794231,232.3445476829273,39707200 -2025-02-10,227.64999389648438,230.58999633789062,227.1999969482422,229.57000732421875,33115600 -2025-02-11,232.6199951171875,235.22999572753906,228.1300048828125,228.1999969482422,53718400 -2025-02-12,236.8699951171875,236.9600067138672,230.67999267578125,231.1999969482422,45243300 -2025-02-13,241.52999877929688,242.33999633789062,235.57000732421875,236.91000366210938,53614100 -2025-02-14,244.60000610351562,245.5500030517578,240.99000549316406,241.25,40896200 +2024-04-11,174.2173614501953,174.63540094951088,167.36970557110288,167.54885233902874,91070300 +2024-04-12,175.72027587890625,177.52176704196785,173.39127675717174,173.44102962389059,101593300 +2024-04-15,171.87841796875,175.7999037342719,171.68930847441092,174.53586805138283,73531800 +2024-04-16,168.583984375,172.94338974194338,167.47920033817573,170.94284143183614,73711200 +2024-04-17,167.21046447753906,169.84800441976404,167.21046447753906,168.81289870293196,50901200 +2024-04-18,166.2549591064453,167.8474456964944,165.7672716683895,167.24031189258443,43122900 +2024-04-19,164.22454833984375,165.6179626751691,163.30887389276177,165.42886837664992,67772100 +2024-04-22,165.0606231689453,166.47394798708703,163.99565958352764,164.74213492186811,48116400 +2024-04-23,166.11561584472656,166.26492000363532,164.14492548116624,164.57291251221173,49537800 +2024-04-24,168.22567749023438,168.50436038665487,165.4288858023208,165.75732156896873,48251800 +2024-04-25,169.09156799316406,169.80818542681567,167.35973999732644,168.73325927633826,50558300 +2024-04-26,168.5043487548828,170.53475474394267,168.38490238817732,169.081624768213,44838400 +2024-04-29,172.6846160888672,175.20272483761752,172.28650201133235,172.555222179483,68169400 +2024-04-30,169.52952575683594,174.16762945183916,169.20107478920735,172.5154270766455,65934800 +2024-05-01,168.5043487548828,171.89832652202932,168.31523926242042,168.78303163206596,50383100 +2024-05-02,172.21682739257812,172.6049939419865,170.08688513916925,171.69926693100732,94214900 +2024-05-03,182.51817321777344,186.12115543095717,181.80155578938326,185.7727942523785,163224100 +2024-05-06,180.85604858398438,183.33433694545525,179.5721025190793,181.49304025440043,78569700 +2024-05-07,181.54278564453125,184.0310366275269,180.46787458450643,182.58785409480518,77305800 +2024-05-08,181.8811798095703,182.20963072654146,180.5972339353124,181.9906634485607,45057100 +2024-05-09,183.70260620117188,183.792179594264,181.2541604804327,181.70204263297254,48983000 +2024-05-10,182.4368438720703,184.47000383075684,181.51992740382525,184.28063783698332,50759500 diff --git a/backend/app/routes/__pycache__/backtest.cpython-312.pyc b/backend/app/routes/__pycache__/backtest.cpython-312.pyc index 5edd37e284ad1bf54b21689b4d1160f7d5e2a5e1..953ceae5dea666651a2b7a28c06128f0fdeff77f 100644 GIT binary patch delta 596 zcmZ8dO=uHA6rQ&;yGb_5#wOL6AJeq9(3D~$Mp7gQs|Z4=3f}Zkw%I9ejY*l^f2ZlTkVaZ16<)5C{Gca)Pzqp8lsqnSCouU5|r4g?A*~xA#OuClW>}j=U9!UWvgK| z>og$smQ62}BK$d=2?gp(qh)#gBV0Q@CDz;-L0ia=B{~cKp;YIxZ;W&n{lnQElJ#|C z+v}cxmg-)7bhob;K9B-S^Cjhk9ui9(f&D%HRN1O+pw*aA|HW(NmHO9e!v7XWp!(8Xer9hH7~*{a|4sOzzDA!h|#Cg>~rxry+AMENAC zB>nvKr~G_BKR=9WNqt)z7~CSWC#&$lOqipnmjbg2z0rtS!M$TKa}M`PAnO@b`g5wR zE7+{^cVx9K@3141(vsvWeuZndTK0N_o@LWQf`GdyXn+CWdksNh7ZpCEi9VXxMddG% vZV`PKvQdkIW3cV<$vWg-U+k-bfX?88q+LWIaEZ7o> zrIB6&|Kib8FM837N6A?)r5-}Dh_@bB8zp$L^L608H*enX=l{;^W!z80!!}S|J@~t_ zXTEc7#2d1Jm7)=|c<6yvpiCj=5uYdB&;vuRVa4RtdK51Oru=}H5~kaAnD@;R$-={dV?q>g;g%d=&C5%SQ2 zu8Cn8yOtFOtebN}pLIP{eMwhyUsrv=!m`Zj54Y6oVocGvB6a(ZIsdpGJz0$x#1vU1 zG?s``hKRo+1oV!;`vw#HFmVKx?^bTN*fjc_W2sZ?14-(DX6_W97u$Cau#jpQy!g8G P6G%=NXy<>Z!M}T_JK}Mh diff --git a/backend/app/routes/backtest.py b/backend/app/routes/backtest.py index b30521f5..06585b64 100644 --- a/backend/app/routes/backtest.py +++ b/backend/app/routes/backtest.py @@ -1,42 +1,21 @@ -# # backend/app/routes/backtest.py -# from fastapi import APIRouter, HTTPException +# from fastapi import APIRouter # from pydantic import BaseModel -# from app.services.strategy_parser import convert_xml_to_json +# from typing import List, Dict, Any # from app.services.backtester import run_backtest # router = APIRouter() -# class StrategyPayload(BaseModel): -# strategy_xml: str - -# @router.post("/backtest") -# def run_backtest_route(payload: StrategyPayload): -# try: -# strategy_json = convert_xml_to_json(payload.strategy_xml) -# result = run_backtest(strategy_json) -# return result -# except Exception as e: -# raise HTTPException(status_code=500, detail=str(e)) - - -# from fastapi import APIRouter, Request -# from pydantic import BaseModel -# from typing import Any, Dict -# from app.services.backtester import run_backtest - -# router = APIRouter() - -# # ✅ Request model # class BacktestRequest(BaseModel): # symbol: str # start_date: str # end_date: str -# code: Dict[str, Any] +# code: List[Dict[str, Any]] # ✅ code is a list of rule dictionaries # @router.post("/backtest") # async def backtest(request_data: BacktestRequest): # try: +# print("Received request:", request_data) # result = run_backtest( # symbol=request_data.symbol, # start_date=request_data.start_date, @@ -47,6 +26,7 @@ # except Exception as e: # return {"error": str(e)} + from fastapi import APIRouter from pydantic import BaseModel from typing import List, Dict, Any @@ -58,7 +38,8 @@ class BacktestRequest(BaseModel): symbol: str start_date: str end_date: str - code: List[Dict[str, Any]] # ✅ code is a list of rule dictionaries + initial_balance: float # ✅ Added balance + code: List[Dict[str, Any]] @router.post("/backtest") async def backtest(request_data: BacktestRequest): @@ -68,7 +49,8 @@ async def backtest(request_data: BacktestRequest): symbol=request_data.symbol, start_date=request_data.start_date, end_date=request_data.end_date, - strategy_json=request_data.code + strategy_json=request_data.code, + initial_balance=request_data.initial_balance # ✅ Pass to backend ) return result except Exception as e: diff --git a/backend/app/services/__pycache__/backtester.cpython-312.pyc b/backend/app/services/__pycache__/backtester.cpython-312.pyc index 3a791d2283cc68ecaa4af09fed6dce485851ef43..6f6879285b6677d22ef5ab0bf55c5647b4163427 100644 GIT binary patch literal 9178 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 literal 4664 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 diff --git a/backend/app/services/backtester.py b/backend/app/services/backtester.py index 8c6516c2..6492ca10 100644 --- a/backend/app/services/backtester.py +++ b/backend/app/services/backtester.py @@ -1,8 +1,212 @@ + + + +# import pandas as pd +# from .data_fetcher import get_data, fetch_and_save_data + +# def calculate_rsi(df, period=14): +# delta = df['Close'].diff() +# gain = delta.where(delta > 0, 0) +# loss = -delta.where(delta < 0, 0) +# avg_gain = gain.rolling(window=period).mean() +# avg_loss = loss.rolling(window=period).mean() +# rs = avg_gain / avg_loss +# df['RSI'] = 100 - (100 / (1 + rs)) +# return df + +# def calculate_ema(df, period=20): +# df[f'EMA_{period}'] = df['Close'].ewm(span=period, adjust=False).mean() +# return df + +# def calculate_macd(df, fast=12, slow=26, signal=9): +# exp1 = df['Close'].ewm(span=fast, adjust=False).mean() +# exp2 = df['Close'].ewm(span=slow, adjust=False).mean() +# macd = exp1 - exp2 +# signal_line = macd.ewm(span=signal, adjust=False).mean() +# df['MACD'] = macd - signal_line +# return df + +# def calculate_bollinger(df, period=20): +# sma = df['Close'].rolling(window=period).mean() +# std = df['Close'].rolling(window=period).std() +# df['Bollinger_Upper'] = sma + (2 * std) +# df['Bollinger_Lower'] = sma - (2 * std) +# return df + +# def calculate_atr(df, period=14): +# high_low = df['High'] - df['Low'] +# high_close = abs(df['High'] - df['Close'].shift()) +# low_close = abs(df['Low'] - df['Close'].shift()) +# tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1) +# df['ATR'] = tr.rolling(window=period).mean() +# return df + +# def evaluate_condition(row, condition): +# if condition["type"] != "LOGIC_COMPARE": +# return False + +# op = condition.get("operator") +# left = condition.get("left", {}) +# right = condition.get("right") +# typ = left.get("type") +# period = left.get("period", 14) + +# if typ == "RSI": +# value = row.get("RSI") +# elif typ == "SMA": +# value = row.get("SMA") +# elif typ == "EMA": +# value = row.get(f"EMA_{period}") +# elif typ == "MACD": +# value = row.get("MACD") +# elif typ == "BOLLINGER": +# band = left.get("band", "upper").upper() +# value = row.get("Bollinger_Upper" if band == "UPPER" else "Bollinger_Lower") +# elif typ == "ATR": +# value = row.get("ATR") +# else: +# return False + +# if pd.isna(value): +# return False + +# if isinstance(right, dict): +# r_typ = right.get("type") +# r_period = right.get("period", 14) +# if r_typ == "RSI": +# right_val = row.get("RSI") +# elif r_typ == "SMA": +# right_val = row.get("SMA") +# elif r_typ == "EMA": +# right_val = row.get(f"EMA_{r_period}") +# elif r_typ == "MACD": +# right_val = row.get("MACD") +# elif r_typ == "ATR": +# right_val = row.get("ATR") +# else: +# right_val = None +# else: +# right_val = right + +# if op == "GT": +# return value > right_val +# elif op == "GTE": +# return value >= right_val +# elif op == "LT": +# return value < right_val +# elif op == "LTE": +# return value <= right_val +# elif op == "EQ": +# return value == right_val + +# return False + +# def process_rule(row, rule): +# if rule["type"] == "IF": +# condition = rule.get("condition") +# if evaluate_condition(row, condition): +# return process_rule(row, rule.get("do", {})) # recurse +# elif rule["type"] == "BUY": +# return "BUY" +# elif rule["type"] == "SELL": +# return "SELL" +# return None + +# def run_backtest(symbol, start_date, end_date, strategy_json, initial_balance): +# fetch_and_save_data(symbol, start_date, end_date) +# df = get_data(symbol, start_date, end_date) + +# if "Date" not in df.columns: +# raise ValueError("'Date' column not found in the data") + +# df["Close"] = pd.to_numeric(df["Close"], errors="coerce") +# df["High"] = pd.to_numeric(df["High"], errors="coerce") +# df["Low"] = pd.to_numeric(df["Low"], errors="coerce") +# df.dropna(subset=["Close", "High", "Low"], inplace=True) +# df["Date"] = pd.to_datetime(df["Date"]) +# df.set_index("Date", inplace=True) + +# # Precompute all required indicators +# for rule in strategy_json: +# def collect_indicators(r): +# if r["type"] == "IF": +# cond = r["condition"] +# left = cond.get("left", {}) +# typ = left.get("type") +# period = left.get("period", 14) + +# if typ == "RSI": +# calculate_rsi(df, period) +# elif typ == "SMA": +# df["SMA"] = df["Close"].rolling(window=period).mean() +# elif typ == "EMA": +# calculate_ema(df, period) +# elif typ == "MACD": +# calculate_macd(df) +# elif typ == "BOLLINGER": +# calculate_bollinger(df, period) +# elif typ == "ATR": +# calculate_atr(df, period) + +# return collect_indicators(r["do"]) +# collect_indicators(rule) + +# balance = initial_balance +# position = None +# entry_price = 0 +# trades = [] + +# for i in range(len(df)): +# row = df.iloc[i] +# date = row.name +# close = row["Close"] + +# for rule in strategy_json: +# signal = process_rule(row, rule) + +# if signal == "BUY" and position is None: +# entry_price = close +# position = close +# trades.append({ +# "date": str(date.date()), +# "action": "BUY", +# "price": round(close, 2) +# }) + +# elif signal == "SELL" and position is not None: +# profit = close - entry_price +# balance += profit +# trades.append({ +# "date": str(date.date()), +# "action": "SELL", +# "price": round(close, 2), +# "pnl": round(profit, 2) +# }) +# position = None + +# final_balance = round(balance, 2) +# returns = df["Close"].pct_change().dropna() +# sharpe = (returns.mean() / returns.std()) * (252 ** 0.5) if returns.std() else 0 + +# return { +# "final_balance": float(final_balance), +# "starting_balance": float(initial_balance), +# "total_trades": len(trades), +# "sharpe_ratio": round(sharpe, 2), +# "trades": [ +# { +# "date": t["date"], +# "action": t["action"], +# "price": float(t["price"]), +# **({"pnl": float(t["pnl"])} if "pnl" in t else {}) +# } for t in trades +# ] +# } import pandas as pd -from .data_fetcher import get_data -from .data_fetcher import fetch_and_save_data +from .data_fetcher import get_data, fetch_and_save_data + +# === Indicator Calculations === -# 📈 RSI calculation def calculate_rsi(df, period=14): delta = df['Close'].diff() gain = delta.where(delta > 0, 0) @@ -13,33 +217,157 @@ def calculate_rsi(df, period=14): df['RSI'] = 100 - (100 / (1 + rs)) return df -# 🚀 Main backtesting function -def run_backtest(symbol, start_date, end_date, strategy_json): - # ✅ Load and clean data +def calculate_sma(df, period=20): + df["SMA"] = df["Close"].rolling(window=period).mean() + return df + +def calculate_ema(df, period=20): + df[f'EMA_{period}'] = df['Close'].ewm(span=period, adjust=False).mean() + return df + +def calculate_macd(df, fast=12, slow=26, signal=9): + exp1 = df['Close'].ewm(span=fast, adjust=False).mean() + exp2 = df['Close'].ewm(span=slow, adjust=False).mean() + macd = exp1 - exp2 + signal_line = macd.ewm(span=signal, adjust=False).mean() + df['MACD'] = macd - signal_line + return df + +def calculate_bollinger(df, period=20): + sma = df['Close'].rolling(window=period).mean() + std = df['Close'].rolling(window=period).std() + df['Bollinger_Upper'] = sma + (2 * std) + df['Bollinger_Lower'] = sma - (2 * std) + return df + +def calculate_atr(df, period=14): + high_low = df['High'] - df['Low'] + high_close = abs(df['High'] - df['Close'].shift()) + low_close = abs(df['Low'] - df['Close'].shift()) + tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1) + df['ATR'] = tr.rolling(window=period).mean() + return df + +# === Condition Evaluation === + +def evaluate_condition(row, condition): + if condition["type"] != "LOGIC_COMPARE": + return False + + op = condition.get("operator") + left = condition.get("left", {}) + right = condition.get("right") + typ = left.get("type") + period = left.get("period", 14) + + # Extract left-side value + if typ == "RSI": + value = row.get("RSI") + elif typ == "SMA": + value = row.get("SMA") + elif typ == "EMA": + value = row.get(f"EMA_{period}") + elif typ == "MACD": + value = row.get("MACD") + elif typ == "BOLLINGER": + band = left.get("band", "upper").upper() + value = row.get("Bollinger_Upper" if band == "UPPER" else "Bollinger_Lower") + elif typ == "ATR": + value = row.get("ATR") + else: + return False + + if pd.isna(value): + return False + + # Extract right-side value + if isinstance(right, dict): + r_typ = right.get("type") + r_period = right.get("period", 14) + if r_typ == "RSI": + right_val = row.get("RSI") + elif r_typ == "SMA": + right_val = row.get("SMA") + elif r_typ == "EMA": + right_val = row.get(f"EMA_{r_period}") + elif r_typ == "MACD": + right_val = row.get("MACD") + elif r_typ == "ATR": + right_val = row.get("ATR") + else: + right_val = None + else: + right_val = right + + if right_val is None or pd.isna(right_val): + return False + + if op == "GT": + return value > right_val + elif op == "GTE": + return value >= right_val + elif op == "LT": + return value < right_val + elif op == "LTE": + return value <= right_val + elif op == "EQ": + return value == right_val + + return False + +# === Strategy Rule Processing === + +def process_rule(row, rule): + if rule["type"] == "IF": + condition = rule.get("condition") + if evaluate_condition(row, condition): + return process_rule(row, rule.get("do", {})) + elif rule["type"] == "BUY": + return "BUY" + elif rule["type"] == "SELL": + return "SELL" + return None + +# === Backtest Engine === + +def run_backtest(symbol, start_date, end_date, strategy_json, initial_balance): fetch_and_save_data(symbol, start_date, end_date) df = get_data(symbol, start_date, end_date) - if "Date" not in df.columns: - raise ValueError("'Date' column not found in the data") - df["Close"] = pd.to_numeric(df["Close"], errors="coerce") - df.dropna(subset=["Close"], inplace=True) + df["High"] = pd.to_numeric(df["High"], errors="coerce") + df["Low"] = pd.to_numeric(df["Low"], errors="coerce") + df.dropna(subset=["Close", "High", "Low"], inplace=True) + df["Date"] = pd.to_datetime(df["Date"]) df.set_index("Date", inplace=True) - # 🛠 Apply indicators (SMA/RSI) + # Precompute indicators for rule in strategy_json: - if rule["type"] == "IF": - cond = rule["condition"] - if cond["type"] == "SMA": - period = cond["period"] - df["SMA"] = df["Close"].rolling(window=period).mean() - elif cond["type"] == "RSI": - period = cond.get("period", 14) - df = calculate_rsi(df, period) - - # 💼 Simulate trades - balance = 10000 + def collect_indicators(r): + if r["type"] == "IF": + cond = r["condition"] + left = cond.get("left", {}) + typ = left.get("type") + period = left.get("period", 14) + + if typ == "RSI": + calculate_rsi(df, period) + elif typ == "SMA": + calculate_sma(df, period) + elif typ == "EMA": + calculate_ema(df, period) + elif typ == "MACD": + calculate_macd(df) + elif typ == "BOLLINGER": + calculate_bollinger(df, period) + elif typ == "ATR": + calculate_atr(df, period) + + collect_indicators(r.get("do", {})) + collect_indicators(rule) + + balance = initial_balance position = None entry_price = 0 trades = [] @@ -50,32 +378,8 @@ def run_backtest(symbol, start_date, end_date, strategy_json): close = row["Close"] for rule in strategy_json: - if rule["type"] != "IF": - continue - - cond = rule["condition"] - action = rule["do"]["type"] - signal = None - - # ➕ SMA condition - if cond["type"] == "SMA": - sma = row.get("SMA") - if pd.isna(sma): continue - op, val = cond["operator"], cond.get("right", 0) - if op == "GT" and close > sma: signal = action - elif op == "LT" and close < sma: signal = action - elif op == "EQ" and close == sma: signal = action - - # ➕ RSI condition - elif cond["type"] == "RSI": - rsi = row.get("RSI") - if pd.isna(rsi): continue - op, val = cond["operator"], cond.get("right", 0) - if op == "GT" and rsi > val: signal = action - elif op == "LT" and rsi < val: signal = action - elif op == "EQ" and rsi == val: signal = action - - # 🟢 BUY + signal = process_rule(row, rule) + if signal == "BUY" and position is None: entry_price = close position = close @@ -85,7 +389,6 @@ def run_backtest(symbol, start_date, end_date, strategy_json): "price": round(close, 2) }) - # 🔴 SELL elif signal == "SELL" and position is not None: profit = close - entry_price balance += profit @@ -97,14 +400,13 @@ def run_backtest(symbol, start_date, end_date, strategy_json): }) position = None - # 📊 Metrics final_balance = round(balance, 2) returns = df["Close"].pct_change().dropna() sharpe = (returns.mean() / returns.std()) * (252 ** 0.5) if returns.std() else 0 return { "final_balance": float(final_balance), - "starting_balance": 10000.0, + "starting_balance": float(initial_balance), "total_trades": len(trades), "sharpe_ratio": round(sharpe, 2), "trades": [ diff --git a/backend/venv/Lib/site-packages/bs4/__pycache__/__init__.cpython-312.pyc b/backend/venv/Lib/site-packages/bs4/__pycache__/__init__.cpython-312.pyc index 4417f548cf42bde234d94775e5b75608a44faa3b..8d7afdca326f1727a9b69e3a20ea6150eb63e960 100644 GIT binary patch delta 23 dcmX@GmFd7%CZ5y0yj%=Gps&W5v5{xXEC5!M2D1PF delta 23 dcmX@GmFd7%CZ5y0yj%=G;B@(a`bM5DvjAb$2yg%Z diff --git a/backend/venv/Lib/site-packages/bs4/__pycache__/element.cpython-312.pyc b/backend/venv/Lib/site-packages/bs4/__pycache__/element.cpython-312.pyc index 0d8161c86f8207dfdd949f11e536a2245c50eb35..0be70790ad398406c6340ececb0b3aecc67d890e 100644 GIT binary patch delta 26 gcmaETjP3O?HlEYGyj%=Gps&W5(a5uvhcSLJ0CbxNc>n+a delta 26 gcmaETjP3O?HlEYGyj%=G;B@(adLz$P9>(~^0EH9@HUIzs diff --git a/backend/venv/Lib/site-packages/bs4/__pycache__/filter.cpython-312.pyc b/backend/venv/Lib/site-packages/bs4/__pycache__/filter.cpython-312.pyc index 715838ce0cd8297ad4afd73a793a320f6e966600..5e6dfb71be7ad7a187ba05dbbf74c3b37eb23da1 100644 GIT binary patch delta 23 dcmdmcpK% diff --git a/backend/venv/Lib/site-packages/bs4/builder/__pycache__/_lxml.cpython-312.pyc b/backend/venv/Lib/site-packages/bs4/builder/__pycache__/_lxml.cpython-312.pyc index da555f6e852de38f1cfd4cea0c1efd774108d267..168c05d3bd11f36600a2789123ef2627255a48bb 100644 GIT binary patch delta 23 dcmccE&v>z)k>@loFBbz4=&LbiY~(rW4ggaC21Nh> delta 23 dcmccE&v>z)k>@loFBbz4I9>jqzLDpsI{;on2m$~A diff --git a/backend/venv/Lib/site-packages/numpy/__pycache__/__config__.cpython-312.pyc b/backend/venv/Lib/site-packages/numpy/__pycache__/__config__.cpython-312.pyc index 79c0a4fd0a03cc8acecbba6927b7e37f39618d23..dfb284609f9ce1504b4d87c4daafe7547ca724b6 100644 GIT binary patch delta 21 bcmeyZ@mquEG%qg~0}$w|F=lM!$rk|tMIHq} delta 21 bcmeyZ@mquEG%qg~0}wb}{-3^)Ctm~rP+$iA diff --git a/backend/venv/Lib/site-packages/python_multipart/__pycache__/__init__.cpython-312.pyc b/backend/venv/Lib/site-packages/python_multipart/__pycache__/__init__.cpython-312.pyc index 387523479cc23daae7cae18279170a5ace2d37e3..fd700457664b51a19e354c3bdbd4d013f02b7841 100644 GIT binary patch delta 21 bcmdnTvX6!5G%qg~0}vRhF=lM!F=7G$HNpfj delta 21 bcmdnTvX6!5G%qg~0}yy#{-3^)$A}34K`aIH diff --git a/backend/venv/Lib/site-packages/python_multipart/__pycache__/decoders.cpython-312.pyc b/backend/venv/Lib/site-packages/python_multipart/__pycache__/decoders.cpython-312.pyc index bab7a8806ba5275c0af100aae50eab84e11fe161..a909e05909936c79847a49a2bba2d21aefb4b8bb 100644 GIT binary patch delta 21 acmZp2Y;xo|&CAQh00f3=j2Rnw$`k-L76g?5 delta 21 bcmZp2Y;xo|&CAQh00f?w|EF){DN_IdL4XEV diff --git a/backend/venv/Lib/site-packages/python_multipart/__pycache__/exceptions.cpython-312.pyc b/backend/venv/Lib/site-packages/python_multipart/__pycache__/exceptions.cpython-312.pyc index 0d83a87a3106641d9fb750670a2e03cbf0570d1e..035b782b0ae87f8d460e14f5c7a4904398a15ea2 100644 GIT binary patch delta 21 bcmX@ld!Co)G%qg~0}vRhF=lM!Im`wCI_3oW delta 21 bcmX@ld!Co)G%qg~0}yy#{-3^)=P(-pMoxk>$ih7M|0*yj%=GV5r8J(a5uvhj9-F0AvUTi2wiq delta 26 gcmX>xk>$ih7M|0*yj%=G;CcCfdLz$P9>zT!0CczrO8@`> diff --git a/frontend/src/App.js b/frontend/src/App.js index 371178d7..0b37c1a5 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,93 +1,5 @@ -// import React, { useState } from 'react'; -// import BlocklyEditor from './components/BlocklyEditor'; -// function App() { -// const [code, setCode] = useState(""); -// return ( -//
-//

AlgoBlocks 🔥

-// -//

Generated Code:

-//
{code}
-//
-// ); -// } - -// export default App; - -// import React, { useState } from 'react'; -// import BlocklyEditor from './components/BlocklyEditor'; - -// import axios from 'axios'; - -// function App() { -// const [codeJson, setCodeJson] = useState(''); -// const [startDate, setStartDate] = useState(''); -// const [endDate, setEndDate] = useState(''); -// const [stockSymbol, setStockSymbol] = useState(''); - -// const handleBacktest = async () => { -// if (!stockSymbol || !startDate || !endDate || !codeJson) { -// alert("Please fill in stock symbol, dates, and strategy blocks."); -// return; -// } - -// try { -// // Ensure codeJson is a valid object, not a string. -// // If it's a string, parse it to a valid object. -// const parsedCode = typeof codeJson === 'string' ? JSON.parse(codeJson) : codeJson; - -// const payload = { -// symbol: stockSymbol.trim(), -// start_date: startDate.trim(), -// end_date: endDate.trim(), -// code: parsedCode, // Send the parsed codeJson as an object -// }; - -// console.log("Sending payload:", payload); - -// const response = await axios.post('http://localhost:8000/api/backtest', payload); - -// alert("✅ Backtest complete:\n" + JSON.stringify(response.data, null, 2)); -// } catch (error) { -// console.error('❌ Backtest failed:', error); -// if (error.response) { -// console.log("Response error data:", error.response.data); -// alert("❌ " + JSON.stringify(error.response.data, null, 2)); -// } else { -// alert("❌ Network or JSON parse error."); -// } -// } -// }; - -// return ( -//
-//

AlgoBlocks 🔥

- -//
-// -// setStockSymbol(e.target.value)} placeholder="AAPL" /> -//
-// -// setStartDate(e.target.value)} /> -//
-// -// setEndDate(e.target.value)} /> -//
- -// - -//
-// -//
- -//
Generated Code:
{codeJson}
-//
-// ); -// } - -// export default App; import React, { useState } from 'react'; import BlocklyEditor from './components/BlocklyEditor'; @@ -98,10 +10,12 @@ function App() { const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const [stockSymbol, setStockSymbol] = useState(''); + const [initialBalance, setInitialBalance] = useState(10000); // ✅ New state + const [result, setResult] = useState(null); const handleBacktest = async () => { - if (!stockSymbol || !startDate || !endDate || !codeJson) { - alert("Please fill in stock symbol, dates, and strategy blocks."); + if (!stockSymbol || !startDate || !endDate || !codeJson || !initialBalance) { + alert("Please fill in all fields including balance."); return; } @@ -112,23 +26,18 @@ function App() { symbol: stockSymbol.trim(), start_date: startDate.trim(), end_date: endDate.trim(), + initial_balance: parseFloat(initialBalance), // ✅ Send balance code: parsedCode, }; - console.log("🔄 Parsed codeJson:", parsedCode); console.log("📦 Sending payload:", JSON.stringify(payload, null, 2)); const response = await axios.post('http://localhost:8000/api/backtest', payload); console.log("✅ Backtest success:", response.data); - alert("✅ Backtest complete:\n" + JSON.stringify(response.data, null, 2)); + setResult(response.data); } catch (error) { console.error('❌ Backtest failed:', error); - if (error.response) { - console.log("🧾 Response error data:", error.response.data); - alert("❌ " + JSON.stringify(error.response.data, null, 2)); - } else { - alert("❌ Network or JSON parse error."); - } + setResult({ error: error.response?.data || "Network or parsing error." }); } }; @@ -138,13 +47,16 @@ function App() {
- setStockSymbol(e.target.value)} placeholder="AAPL" /> + setStockSymbol(e.target.value)} placeholder="e.g. AAPL" />
setStartDate(e.target.value)} />
setEndDate(e.target.value)} /> +
+ + setInitialBalance(e.target.value)} />
@@ -153,9 +65,14 @@ function App() { -
Generated Code:
{codeJson}
+ {result && ( +
+ 📊 Backtest Result:
+ {typeof result === 'object' ? JSON.stringify(result, null, 2) : result} +
+ )} ); } -export default App; +export default App; \ No newline at end of file diff --git a/frontend/src/components/BlocklyEditor.js b/frontend/src/components/BlocklyEditor.js index a20ce7c4..d49a8be6 100644 --- a/frontend/src/components/BlocklyEditor.js +++ b/frontend/src/components/BlocklyEditor.js @@ -1,74 +1,4 @@ -// import React, { useRef, useEffect } from "react"; -// import * as Blockly from "blockly"; -// import { javascriptGenerator } from "blockly/javascript"; -// import { jsonGenerator } from '../generators/jsonGenerator'; -// import { defineCustomBlocks } from './customBlocks'; - -// function BlocklyEditor({ onCodeChange }) { -// const blocklyDiv = useRef(null); - -// // ✅ Toolbox with Categories -// const toolbox = { -// kind: "categoryToolbox", -// contents: [ -// { -// kind: "category", -// name: "Logic", -// colour: "#5C81A6", -// contents: [ -// { kind: "block", type: "controls_if" }, -// { kind: "block", type: "logic_compare" }, -// ], -// }, -// { -// kind: "category", -// name: "Math", -// colour: "#5CA65C", -// contents: [ -// { kind: "block", type: "math_number" }, -// ], -// }, -// { -// kind: "category", -// name: "Indicators", -// colour: "#A65C81", -// contents: [ -// { kind: "block", type: "rsi_block" }, -// { kind: "block", type: "sma_block" }, -// ], -// }, -// { -// kind: "category", -// name: "Actions", -// colour: "#A6745C", -// contents: [ -// { kind: "block", type: "buy_block" }, -// { kind: "block", type: "sell_block" }, -// ], -// }, -// ], -// }; - -// useEffect(() => { -// defineCustomBlocks(); // ✅ Register custom blocks before injecting Blockly - -// const workspace = Blockly.inject(blocklyDiv.current, { -// toolbox: toolbox, -// }); - -// workspace.addChangeListener(() => { -// const json = jsonGenerator.workspaceToJson(workspace); -// onCodeChange(JSON.stringify(json, null, 2)); // ✅ send JSON to parent -// }); - -// return () => workspace.dispose(); -// }, [onCodeChange]); - -// return
; -// } - -// export default BlocklyEditor; import React, { useRef, useEffect } from "react"; import * as Blockly from "blockly"; @@ -111,7 +41,7 @@ function BlocklyEditor({ onCodeChange }) { { kind: "block", type: "macd_block" }, { kind: "block", type: "bollinger_block" }, { kind: "block", type: "atr_block" }, - { kind: "block", type: "stochastic_block" }, + // { kind: "block", type: "stochastic_block" }, ], }, { diff --git a/frontend/src/components/StrategySelector.js b/frontend/src/components/StrategySelector.js index 26433e35..64ba8a32 100644 --- a/frontend/src/components/StrategySelector.js +++ b/frontend/src/components/StrategySelector.js @@ -1,69 +1,4 @@ -// import React, { useState } from 'react'; -// function StrategySelector({ onSubmit }) { -// const [indicator, setIndicator] = useState('SMA'); -// const [condition, setCondition] = useState('>'); -// const [value, setValue] = useState(20); -// const [stock, setStock] = useState('AAPL'); - -// const handleSubmit = (e) => { -// e.preventDefault(); - -// const strategy = { -// stock, -// conditions: [ -// { -// indicator: indicator.toLowerCase(), -// condition, -// value: parseFloat(value), -// }, -// ], -// }; - -// onSubmit(strategy); -// }; - -// return ( -//
-//

Build Your Strategy 🧱

- -// -// setStock(e.target.value)} -// placeholder="e.g. AAPL" -// /> -//

- -// -// -//

- -// -// -//

- -// -// setValue(e.target.value)} -// /> -//

- -// -//
-// ); -// } - -// export default StrategySelector; import React, { useState } from 'react'; diff --git a/frontend/src/components/customBlocks.js b/frontend/src/components/customBlocks.js index 055a4ad9..d522e8c3 100644 --- a/frontend/src/components/customBlocks.js +++ b/frontend/src/components/customBlocks.js @@ -1,60 +1,3 @@ -// import * as Blockly from 'blockly'; - -// export function defineCustomBlocks() { -// // RSI Block -// Blockly.Blocks['rsi_block'] = { -// init: function () { -// this.appendDummyInput() -// .appendField("RSI") -// .appendField("Period") -// .appendField(new Blockly.FieldNumber(14, 1), "PERIOD"); -// this.setOutput(true, "Number"); -// this.setColour(230); -// this.setTooltip("Calculates RSI"); -// this.setHelpUrl(""); -// }, -// }; - -// // ✅ SMA Block -// Blockly.Blocks['sma_block'] = { -// init: function () { -// this.appendDummyInput() -// .appendField("SMA") -// .appendField("Period") -// .appendField(new Blockly.FieldNumber(20, 1), "PERIOD"); -// this.setOutput(true, "Number"); -// this.setColour(230); -// this.setTooltip("Simple Moving Average"); -// this.setHelpUrl(""); -// }, -// }; - -// // ✅ Buy Block -// Blockly.Blocks['buy_block'] = { -// init: function () { -// this.appendDummyInput() -// .appendField("BUY"); -// this.setPreviousStatement(true, null); -// this.setNextStatement(true, null); -// this.setColour(120); -// this.setTooltip("Place a buy order"); -// this.setHelpUrl(""); -// }, -// }; - -// // ✅ Sell Block -// Blockly.Blocks['sell_block'] = { -// init: function () { -// this.appendDummyInput() -// .appendField("SELL"); -// this.setPreviousStatement(true, null); -// this.setNextStatement(true, null); -// this.setColour(0); -// this.setTooltip("Place a sell order"); -// this.setHelpUrl(""); -// }, -// }; -// } import * as Blockly from 'blockly'; @@ -149,21 +92,39 @@ export function defineCustomBlocks() { }, }; - // ✅ Stochastic Oscillator Block - Blockly.Blocks['stochastic_block'] = { - init: function () { - this.appendDummyInput() - .appendField("Stochastic") - .appendField("K Period") - .appendField(new Blockly.FieldNumber(14, 1), "KPERIOD") - .appendField("D Period") - .appendField(new Blockly.FieldNumber(3, 1), "DPERIOD"); - this.setOutput(true, "Number"); - this.setColour(230); - this.setTooltip("Stochastic Oscillator"); - this.setHelpUrl(""); - }, - }; + // // ✅ Stochastic Oscillator Block + // Blockly.Blocks['stochastic_block'] = { + // init: function () { + // this.appendDummyInput() + // .appendField("Stochastic") + // .appendField("K Period") + // .appendField(new Blockly.FieldNumber(14, 1), "KPERIOD") + // .appendField("D Period") + // .appendField(new Blockly.FieldNumber(3, 1), "DPERIOD"); + // this.setOutput(true, "Number"); + // this.setColour(230); + // this.setTooltip("Stochastic Oscillator"); + // this.setHelpUrl(""); + // }, + // }; + + // Blockly.Blocks['stochastic_block'] = { + // init: function () { + // this.appendDummyInput() + // .appendField("Stochastic") + // .appendField("K Period") + // .appendField(new Blockly.FieldNumber(14, 1), "KPERIOD") + // .appendField("D Period") + // .appendField(new Blockly.FieldNumber(3, 1), "DPERIOD") + // .appendField("Target") + // .appendField(new Blockly.FieldNumber(80, 1, 100), "TARGET"); // Added target with default value + // this.setOutput(true, "Number"); + // this.setColour(230); + // this.setTooltip("Stochastic Oscillator"); + // this.setHelpUrl(""); + // }, + // }; + // ✅ Buy Block Blockly.Blocks['buy_block'] = { diff --git a/frontend/src/generators/jsonGenerator.js b/frontend/src/generators/jsonGenerator.js index 0e03b963..4476a08e 100644 --- a/frontend/src/generators/jsonGenerator.js +++ b/frontend/src/generators/jsonGenerator.js @@ -1,81 +1,4 @@ -// import * as Blockly from 'blockly'; - -// export const jsonGenerator = { -// workspaceToJson(workspace) { -// const topBlocks = workspace.getTopBlocks(true); -// return topBlocks.map(block => parseBlock(block)).filter(b => b !== null); -// } -// }; - -// function parseBlock(block) { -// if (!block) return null; - -// switch (block.type) { -// case 'controls_if': { -// const conditionBlock = block.getInputTargetBlock('IF0'); -// const doBlock = block.getInputTargetBlock('DO0'); - -// return { -// type: 'IF', -// condition: parseBlock(conditionBlock), -// do: parseBlock(doBlock), -// }; -// } - -// case 'logic_compare': { -// const operator = block.getFieldValue('OP'); -// const left = parseBlock(block.getInputTargetBlock('A')); -// const right = parseBlock(block.getInputTargetBlock('B')); - -// return { -// type: 'LOGIC_COMPARE', -// operator: operator, -// left: left, -// right: right -// }; -// } - -// case 'rsi_block': { -// const period = block.getFieldValue('PERIOD'); -// return { -// type: 'RSI', -// period: parseInt(period, 10) -// }; -// } - -// case 'sma_block': { -// const period = block.getFieldValue('PERIOD'); -// return { -// type: 'SMA', -// period: parseInt(period, 10) -// }; -// } - -// case 'ema_block': { -// const period = block.getFieldValue('PERIOD'); -// return { -// type: 'EMA', -// period: parseInt(period, 10) -// }; -// } - -// case 'buy_block': -// return { type: 'BUY' }; - -// case 'sell_block': -// return { type: 'SELL' }; - -// case 'math_number': { -// const num = block.getFieldValue('NUM'); -// return parseFloat(num); -// } - -// default: -// return { -// type: block.type -// }; -// } -// } + import * as Blockly from 'blockly'; @@ -166,16 +89,33 @@ function parseBlock(block) { }; } + // case 'stochastic_block': { + // const kPeriod = parseInt(block.getFieldValue('K_PERIOD')); + // const dPeriod = parseInt(block.getFieldValue('D_PERIOD')); + // const target = block.getFieldValue('TARGET'); // "%K" or "%D" + // return { + // type: 'STOCHASTIC', + // kPeriod: parseInt(kPeriod), + // dPeriod: parseInt(dPeriod), + // target: target + // }; + // } case 'stochastic_block': { - const kPeriod = parseInt(block.getFieldValue('K_PERIOD'), 10); - const dPeriod = parseInt(block.getFieldValue('D_PERIOD'), 10); + const kPeriod = block.getFieldValue('KPERIOD'); + const dPeriod = block.getFieldValue('DPERIOD'); + const target = block.getFieldValue('TARGET'); // Capture the target value + + // Use a fallback if target is invalid + const parsedTarget = (target && !isNaN(target)) ? parseInt(target, 10) : 80; // Default to 80 if invalid + return { type: 'STOCHASTIC', - kPeriod, - dPeriod, + kPeriod: parseInt(kPeriod, 10), + dPeriod: parseInt(dPeriod, 10), + target: parsedTarget // Ensure target is not null }; } - + case 'buy_block': return { type: 'BUY' };