finmiti 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
finmiti-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: finmiti
3
+ Version: 0.0.1
4
+ Summary: Persnal project for stock market data analysis
5
+ Author-email: Darshan Rathod <darshan.rathod1994@gmail.com>
6
+ Maintainer-email: Darshan Rathod <darshan.rathod1994@gmail.com>
7
+ License: MIT
8
+ Project-URL: Homepage, https://dev-ddr.github.io/finmiti/
9
+ Project-URL: Repository, https://github.com/dev-ddr/finmiti
10
+ Project-URL: Issues, https://github.com/dev-ddr/finmiti/issues
11
+ Keywords: finance,stock-market,quant,research,personal
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.12
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: numpy==2.3.5
18
+ Requires-Dist: pandas==2.3.3
19
+ Requires-Dist: pyyaml==6.0.3
20
+ Requires-Dist: duckdb==1.4.2
21
+ Requires-Dist: pyarrow==22.0.0
22
+ Requires-Dist: py5paisa==0.7.21.2
23
+ Requires-Dist: pydantic
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Requires-Dist: ruff; extra == "dev"
27
+ Requires-Dist: jupyter; extra == "dev"
28
+ Requires-Dist: notebook; extra == "dev"
29
+ Requires-Dist: sphinx; extra == "dev"
30
+ Requires-Dist: mkdocs; extra == "dev"
31
+ Requires-Dist: mkdocs-material; extra == "dev"
32
+ Requires-Dist: mkdocstrings[python]; extra == "dev"
33
+ Requires-Dist: nbconvert; extra == "dev"
34
+ Requires-Dist: matplotlib; extra == "dev"
35
+ Requires-Dist: plotly; extra == "dev"
36
+
37
+ # Finmiti
38
+
39
+ **This project is developed for my personal use.** I am developing this to keep the documentation, architecture and pipeline consistent so that I can focus more on developing strategies instead of developing pipelines.
40
+
41
+ Visit [Finmiti](https://dev-ddr.github.io/finmetry/) guide for further steps.
42
+
43
+ > This project is solely developed for my personal use. I am publishing this only to keep myself updated and to remove the headache of setting up the framework again and again.
44
+
45
+ ---
46
+
47
+ **Finmiti is a research-first quantitative trading framework** designed to keep
48
+ strategy logic, execution logic, and accounting logic strictly separated.
49
+
50
+ It exists to eliminate repeated reinvention of trading pipelines, so you can
51
+ focus on **researching strategies**, not rebuilding infrastructure.
52
+
53
+
54
+ ## What Finmiti Is (and Is Not)
55
+
56
+ Finmiti is:
57
+
58
+ - a framework for systematic trading research
59
+ - equally suited for backtesting and live trading
60
+ - opinionated by design
61
+ - built around explicit, auditable abstractions
62
+
63
+ Finmiti is **not**:
64
+
65
+ - a strategy library
66
+ - a signal generator
67
+ - a black-box trading system
68
+
69
+ If you want flexibility at the cost of correctness, this framework will feel restrictive.
70
+ That restriction is intentional.
71
+
72
+
73
+ ## The Core Trading Loop
74
+
75
+ Every strategy in finmiti follows the same explicit loop:
76
+
77
+ ```text
78
+ Market Data → Strategy → Orders → Portfolio → Execution → Accounting
79
+ ````
80
+
81
+ Each stage is implemented as a **separate module** with strict responsibilities.
82
+
83
+ This guarantees that:
84
+
85
+ * strategies remain stateless
86
+ * execution assumptions are explicit
87
+ * accounting is consistent
88
+ * backtests can be trusted
89
+ * live trading reuses the same abstractions
90
+
91
+ ## Major Modules
92
+
93
+ Finmiti is organized into the following conceptual modules:
94
+
95
+ ### [Client Handling](https://dev-ddr.github.io/finmetry/concepts/client_handling_module/)
96
+
97
+ Handles interaction with external systems such as broker APIs and live data feeds.
98
+ Keeps the rest of the framework broker-agnostic.
99
+
100
+ ### [Stocks](https://dev-ddr.github.io/finmetry/concepts/stocks_handling_module/)
101
+
102
+ Manages symbols, historical data, and OHLCV storage.
103
+ Acts as the foundation for all market data access.
104
+
105
+ ### [Strategy](https://dev-ddr.github.io/finmetry/concepts/strategy_handling_module/)
106
+
107
+ Consumes immutable market snapshots and emits **order intent only**.
108
+ Strategies never manage cash, positions, or execution details.
109
+
110
+ ### [Orders](https://dev-ddr.github.io/finmetry/concepts/order/)
111
+
112
+ Orders are the contract between strategy, portfolio, and execution.
113
+ They represent intent, not outcome.
114
+
115
+ ### [Executioners](https://dev-ddr.github.io/finmetry/concepts/executioners/)
116
+
117
+ Simulate (or connect to) market reality:
118
+ slippage, brokerage, partial fills, or live execution.
119
+
120
+ ### [Portfolio](https://dev-ddr.github.io/finmetry/concepts/portfolio_handling_module/)
121
+
122
+ The single source of truth for positions, cash, and PnL.
123
+ All state mutation happens here.
124
+
125
+ ### [Backtesting](https://dev-ddr.github.io/finmetry/concepts/backtesting_handling_module/)
126
+
127
+ Pure orchestration.
128
+ Iterates over time and wires everything together without adding logic.
129
+
130
+ ## Final Note
131
+
132
+ > I have built this for my personal use in mind.
133
+
134
+
135
+
136
+
@@ -0,0 +1,100 @@
1
+ # Finmiti
2
+
3
+ **This project is developed for my personal use.** I am developing this to keep the documentation, architecture and pipeline consistent so that I can focus more on developing strategies instead of developing pipelines.
4
+
5
+ Visit [Finmiti](https://dev-ddr.github.io/finmetry/) guide for further steps.
6
+
7
+ > This project is solely developed for my personal use. I am publishing this only to keep myself updated and to remove the headache of setting up the framework again and again.
8
+
9
+ ---
10
+
11
+ **Finmiti is a research-first quantitative trading framework** designed to keep
12
+ strategy logic, execution logic, and accounting logic strictly separated.
13
+
14
+ It exists to eliminate repeated reinvention of trading pipelines, so you can
15
+ focus on **researching strategies**, not rebuilding infrastructure.
16
+
17
+
18
+ ## What Finmiti Is (and Is Not)
19
+
20
+ Finmiti is:
21
+
22
+ - a framework for systematic trading research
23
+ - equally suited for backtesting and live trading
24
+ - opinionated by design
25
+ - built around explicit, auditable abstractions
26
+
27
+ Finmiti is **not**:
28
+
29
+ - a strategy library
30
+ - a signal generator
31
+ - a black-box trading system
32
+
33
+ If you want flexibility at the cost of correctness, this framework will feel restrictive.
34
+ That restriction is intentional.
35
+
36
+
37
+ ## The Core Trading Loop
38
+
39
+ Every strategy in finmiti follows the same explicit loop:
40
+
41
+ ```text
42
+ Market Data → Strategy → Orders → Portfolio → Execution → Accounting
43
+ ````
44
+
45
+ Each stage is implemented as a **separate module** with strict responsibilities.
46
+
47
+ This guarantees that:
48
+
49
+ * strategies remain stateless
50
+ * execution assumptions are explicit
51
+ * accounting is consistent
52
+ * backtests can be trusted
53
+ * live trading reuses the same abstractions
54
+
55
+ ## Major Modules
56
+
57
+ Finmiti is organized into the following conceptual modules:
58
+
59
+ ### [Client Handling](https://dev-ddr.github.io/finmetry/concepts/client_handling_module/)
60
+
61
+ Handles interaction with external systems such as broker APIs and live data feeds.
62
+ Keeps the rest of the framework broker-agnostic.
63
+
64
+ ### [Stocks](https://dev-ddr.github.io/finmetry/concepts/stocks_handling_module/)
65
+
66
+ Manages symbols, historical data, and OHLCV storage.
67
+ Acts as the foundation for all market data access.
68
+
69
+ ### [Strategy](https://dev-ddr.github.io/finmetry/concepts/strategy_handling_module/)
70
+
71
+ Consumes immutable market snapshots and emits **order intent only**.
72
+ Strategies never manage cash, positions, or execution details.
73
+
74
+ ### [Orders](https://dev-ddr.github.io/finmetry/concepts/order/)
75
+
76
+ Orders are the contract between strategy, portfolio, and execution.
77
+ They represent intent, not outcome.
78
+
79
+ ### [Executioners](https://dev-ddr.github.io/finmetry/concepts/executioners/)
80
+
81
+ Simulate (or connect to) market reality:
82
+ slippage, brokerage, partial fills, or live execution.
83
+
84
+ ### [Portfolio](https://dev-ddr.github.io/finmetry/concepts/portfolio_handling_module/)
85
+
86
+ The single source of truth for positions, cash, and PnL.
87
+ All state mutation happens here.
88
+
89
+ ### [Backtesting](https://dev-ddr.github.io/finmetry/concepts/backtesting_handling_module/)
90
+
91
+ Pure orchestration.
92
+ Iterates over time and wires everything together without adding logic.
93
+
94
+ ## Final Note
95
+
96
+ > I have built this for my personal use in mind.
97
+
98
+
99
+
100
+
@@ -0,0 +1,74 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "finmiti"
7
+ version = "0.0.1"
8
+ description = "Persnal project for stock market data analysis"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+
12
+ authors = [
13
+ { name = "Darshan Rathod", email = "darshan.rathod1994@gmail.com" }
14
+ ]
15
+
16
+ maintainers = [
17
+ { name = "Darshan Rathod", email = "darshan.rathod1994@gmail.com" }
18
+ ]
19
+
20
+ license = { text = "MIT" }
21
+
22
+ keywords = ["finance", "stock-market", "quant", "research", "personal"]
23
+
24
+ classifiers = [
25
+ "Development Status :: 4 - Beta",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Operating System :: OS Independent",
28
+ ]
29
+
30
+ dependencies = [
31
+ "numpy==2.3.5",
32
+ "pandas==2.3.3",
33
+ "pyyaml==6.0.3",
34
+ "duckdb==1.4.2",
35
+ "pyarrow==22.0.0",
36
+ "py5paisa==0.7.21.2",
37
+ "pydantic",
38
+ ]
39
+
40
+ [project.optional-dependencies]
41
+ # dev = ["pytest", "pytest-cov", "ruff", "jupyter", "notebook"]
42
+ # docs = ["sphinx", "mkdocs", "nbconvert"]
43
+ # viz = ["matplotlib", "plotly"]
44
+ dev = [
45
+ "pytest", "ruff", "jupyter", "notebook",
46
+ "sphinx", "mkdocs", "mkdocs-material", "mkdocstrings[python]", "nbconvert",
47
+ "matplotlib", "plotly"
48
+ ]
49
+
50
+ [project.urls]
51
+ Homepage = "https://dev-ddr.github.io/finmiti/"
52
+ Repository = "https://github.com/dev-ddr/finmiti"
53
+ Issues = "https://github.com/dev-ddr/finmiti/issues"
54
+
55
+
56
+ [tool.setuptools]
57
+ package-dir = {"" = "src"}
58
+
59
+ # This is crucial for telling setuptools where your actual source code is.
60
+ # It tells setuptools to find packages within the 'src' directory.
61
+ [tool.setuptools.packages.find]
62
+ where = ["src"]
63
+ exclude = ["tests*", "legacy*", "experimental*"]
64
+
65
+
66
+ [tool.ruff]
67
+ line-length = 310
68
+ select = ["E", "F", "I"] # Errors, pyflakes, imports
69
+ ignore = ["E501"] # Disable long-line check (black handles it)
70
+ src = ["src"]
71
+
72
+ [tool.ruff.format]
73
+ quote-style = "double"
74
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,12 @@
1
+ from importlib.metadata import version
2
+ __version__ = version("finmetry")
3
+
4
+
5
+ from . import clients
6
+ from .stocks_handler import Stock, StockDict
7
+ from . import constants
8
+ from .strategy_handler import StrategyBase, StgDataLoader
9
+ from .portfolio_handler import Portfolio, Account
10
+ from .backtest_handler import Backtester
11
+
12
+ from .utils import *
@@ -0,0 +1,2 @@
1
+
2
+ from .base import Backtester
@@ -0,0 +1,19 @@
1
+ from ..portfolio_handler import Portfolio
2
+ from ..executioners import ExecutionModel
3
+ from ..strategy_handler import StgDataLoader, StrategyBase
4
+
5
+ class Backtester:
6
+ def __init__(self, data_loader: StgDataLoader, strategy: StrategyBase, portfolio: Portfolio):
7
+ self.data_loader = data_loader
8
+ self.strategy = strategy
9
+ self.portfolio = portfolio
10
+
11
+ def run(self):
12
+ for market_data in self.data_loader:
13
+ entry_orders = self.strategy(market_data)
14
+ exit_orders = self.portfolio.get_exit_orders(market_data)
15
+ ### keeping exit_orders first to avoid cash going negative
16
+ all_orders = exit_orders + entry_orders
17
+ for order in all_orders:
18
+ self.portfolio.on_order(order)
19
+ self.portfolio.mark_to_market(market_data)
@@ -0,0 +1,2 @@
1
+
2
+ from . import client_5paisa
@@ -0,0 +1,242 @@
1
+ """
2
+ This module contains the objects related to 5paisa client
3
+
4
+ @author: Rathod Darshan
5
+ """
6
+
7
+ import py5paisa as p5
8
+ import pandas as pd
9
+ import datetime as dtm
10
+ from typing import Union, TypedDict
11
+
12
+ from ..stocks_handler import Stock, StockDict
13
+
14
+ from ..constants import INTERVAL
15
+
16
+
17
+ class ScripMaster:
18
+ """ScripMaster contains all the scipts of 5paisa client.
19
+
20
+ To get the name and symbol of any script, this class needs to be accessed. This class just filters the data from single .csv.
21
+ """
22
+
23
+ def __init__(self, filepath: str = None) -> None:
24
+ """Initializes the ScripMaster class.
25
+
26
+ Loads the .csv file into data attribute.
27
+
28
+ Parameters
29
+ ----------
30
+ filepath : str, optional
31
+ filepath to .csv file. If filepath is given then it reads the file, else it will download the file. by default None
32
+ """
33
+ if filepath is not None:
34
+ self.data = pd.read_pickle(filepath)
35
+ else:
36
+ self.data = pd.read_csv("https://images.5paisa.com/website/scripmaster-csv-format.csv")
37
+
38
+ self.data["Expiry"] = pd.to_datetime(self.data["Expiry"], format="%Y-%m-%d %H:%M:%S")
39
+ self.data["Name"] = self.data["Name"].apply(str.upper)
40
+ self.data["Symbol"] = self.data["Name"]
41
+
42
+ def __repr__(self):
43
+ return f"scrip master data"
44
+
45
+ def __call__(self):
46
+ return self.data
47
+
48
+ def save(self, filepath: str) -> None:
49
+ """saves the scrip master data
50
+
51
+ Parameters
52
+ ----------
53
+ filepath : str
54
+ filepath with filename with .pkl extention
55
+
56
+ Returns
57
+ -------
58
+ _type_
59
+ None
60
+ """
61
+ return self.data.to_pickle(filepath)
62
+
63
+ def get_scrip(self, stock: Stock) -> pd.DataFrame:
64
+ """returns the scrips of the stock
65
+
66
+ Parameters
67
+ ----------
68
+ stock : Stock
69
+ a stock object
70
+
71
+ Returns
72
+ -------
73
+ pd.DataFrame
74
+ Scrip data of a given stock
75
+ """
76
+ try:
77
+ return stock.scrip
78
+ except:
79
+ pass
80
+ d1 = self.data
81
+ f1 = (d1["Exch"] == stock.exchange) & (d1["ExchType"] == stock.exchange_type) & (d1["Symbol"] == stock.symbol)
82
+ f2 = (d1["Series"] == "EQ") | (d1["Series"] == "XX")
83
+ d2 = d1[f1 & f2]
84
+ if d2.empty:
85
+ raise ValueError(f"No Scrip found for {stock.symbol} in scrip_master")
86
+ d2 = d2.set_index("Name")
87
+ ### setting the scrip to stock object
88
+ stock.scrip = d2
89
+ return d2
90
+
91
+
92
+ class Client5paisaCred(TypedDict):
93
+ APP_NAME: str
94
+ APP_SOURCE: str
95
+ USER_ID: str
96
+ PASSWORD: str
97
+ USER_KEY: str
98
+ ENCRYPTION_KEY: str
99
+
100
+
101
+ class Client5paisa(p5.FivePaisaClient):
102
+ def __init__(self, totp: str, mpin: str, client_code: str, cred: Client5paisaCred, scrip_master: ScripMaster = None, **kwargs):
103
+ super().__init__(cred=cred)
104
+ self.get_totp_session(client_code, f"{totp}", mpin)
105
+
106
+ print("downloading the scrip-master")
107
+
108
+ self.scrip_master = ScripMaster() if scrip_master is None else scrip_master
109
+ return
110
+
111
+ def download_historical_data(
112
+ self,
113
+ stock: Stock,
114
+ interval: INTERVAL = INTERVAL.one_day,
115
+ start: Union[str, dtm.datetime] = "2023-01-01",
116
+ end: Union[str, dtm.datetime] = "2023-03-30",
117
+ ) -> pd.DataFrame:
118
+ """Downloads the historical data and saves it to local drive or in Stock.data variable.
119
+
120
+ Parameters
121
+ ----------
122
+ stock : Stock
123
+ Stock object
124
+ interval : str, optional
125
+ time interval of data. it should be within [1m,5m,10m,15m,30m,60m,1d], by default "1d"
126
+ start : Union[str, dtm.datetime], optional
127
+ start date of the data. The data for this date will be downloaded, by default "2023-01-01"
128
+ end : Union[str, dtm.datetime], optional
129
+ end date of the data. The data for this date will be downloaded, by default "2023-03-30"
130
+
131
+ Returns
132
+ ----------
133
+ pd.DataFrame
134
+ A dataframe containing a historical data.
135
+
136
+ """
137
+ # scrip = self.scrip_master.get_scrip(stock)
138
+
139
+ # if isinstance(start, dtm.datetime):
140
+ # start = start.strftime("%Y-%m-%d")
141
+ # if isinstance(end, dtm.datetime):
142
+ # end = end.strftime("%Y-%m-%d")
143
+
144
+ # df = self.historical_data(stock.exchange, stock.exchange_type, scrip.loc[stock.symbol, "Scripcode"], interval.value, start, end)
145
+ # df.columns = ["Datetime", "Open", "High", "Low", "Close", "Volume"]
146
+ # df["Datetime"] = pd.to_datetime(df["Datetime"])
147
+ # df = df.set_index("Datetime")
148
+
149
+ # return df
150
+
151
+ scrip = self.scrip_master.get_scrip(stock)
152
+ if isinstance(start, str):
153
+ start_dt = dtm.datetime.strptime(start, "%Y-%m-%d")
154
+ else:
155
+ start_dt = start
156
+
157
+ if isinstance(end, str):
158
+ end_dt = dtm.datetime.strptime(end, "%Y-%m-%d")
159
+ else:
160
+ end_dt = end
161
+
162
+ def _fetch_chunk(s: dtm.datetime, e: dtm.datetime) -> pd.DataFrame:
163
+ df = self.historical_data(stock.exchange, stock.exchange_type, scrip.loc[stock.symbol, "Scripcode"], interval.value, s.strftime("%Y-%m-%d"), e.strftime("%Y-%m-%d"))
164
+ if df is None or df.empty:
165
+ return pd.DataFrame()
166
+ df.columns = ["Datetime", "Open", "High", "Low", "Close", "Volume"]
167
+ df["Datetime"] = pd.to_datetime(df["Datetime"])
168
+ return df.set_index("Datetime")
169
+
170
+ dfs = []
171
+ curr_start = start_dt
172
+
173
+ while curr_start < end_dt:
174
+ curr_end = min(curr_start + dtm.timedelta(days=90), end_dt)
175
+ chunk = _fetch_chunk(curr_start, curr_end)
176
+ if not chunk.empty:
177
+ dfs.append(chunk)
178
+
179
+ curr_start = curr_end
180
+
181
+ if not dfs:
182
+ return pd.DataFrame()
183
+
184
+ df = pd.concat(dfs).sort_index()
185
+ df = df.loc[~df.index.duplicated(keep="first")]
186
+
187
+ return df
188
+
189
+ def get_market_depth(self, stockdict: Union[list[Stock], StockDict]) -> pd.DataFrame:
190
+ """Gets the market depth for given list of Stocks
191
+
192
+ Parameters
193
+ ----------
194
+ StockDict : list[Stock]|StockDict
195
+ list of Stock class instances or StockDict type object.
196
+
197
+ Returns
198
+ -------
199
+ _pd.DataFrame
200
+ Live Market Depth for all the Stocks in list.
201
+ """
202
+ scrips = pd.concat(self.scrip_master.get_scrip(stock) for stock in stockdict)
203
+ a = scrips.rename(columns={"Exch": "Exchange", "ExchType": "ExchangeType"})[["Exchange", "ExchangeType", "Symbol"]].to_dict(orient="records")
204
+ d1 = pd.DataFrame(self.fetch_market_depth_by_symbol(a)["Data"])
205
+
206
+ d1.set_index("ScripCode", inplace=True)
207
+ d1["Datetime"] = dtm.datetime.now()
208
+ scrips.set_index("Scripcode", inplace=True)
209
+ d1["Symbol"] = scrips["Symbol"]
210
+ d1.set_index("Symbol", inplace=True)
211
+ d1.rename(columns={"Close": "PrevClose"}, inplace=True)
212
+ d1.rename(columns={"LastTradedPrice": "Close"}, inplace=True)
213
+ return d1
214
+
215
+ def update_stock_histdata0_to_ltp(self, stockdict: Union[list[Stock], StockDict]) -> None:
216
+ """Updates the market depth to stock.hist_data0 attribute
217
+
218
+ Parameters
219
+ ----------
220
+ StockDict : list[Stock]|StockDict
221
+ list of Stock class instances or StockDict type object.
222
+
223
+ Returns
224
+ -------
225
+ None
226
+ """
227
+ d1 = self.get_market_depth(stockdict)
228
+ d1 = d1[["Datetime", "Open", "High", "Low", "Close", "Volume"]]
229
+ d1["Datetime"] = d1["Datetime"].dt.normalize()
230
+ ### Convert datatypes of specific columns
231
+ float_cols = ["Open", "High", "Low", "Close"]
232
+ d1[float_cols] = d1[float_cols].astype(float)
233
+ d1["Volume"] = d1["Volume"].astype(int)
234
+
235
+ for s1 in stockdict:
236
+ try:
237
+ d = d1.loc[s1.symbol]
238
+ s1.hist_data0.loc[d.Datetime] = d
239
+ except:
240
+ print(f"Error in {s1.symbol}")
241
+ continue
242
+ return