{"id":13740353,"url":"https://github.com/rafa-rod/pytrendseries","last_synced_at":"2026-04-06T06:34:37.394Z","repository":{"id":45391401,"uuid":"368020444","full_name":"rafa-rod/pytrendseries","owner":"rafa-rod","description":"Detect trend in time series, drawdown, drawdown within a constant look-back window , maximum drawdown, time underwater.","archived":false,"fork":false,"pushed_at":"2026-04-04T16:00:40.000Z","size":5882,"stargazers_count":163,"open_issues_count":0,"forks_count":26,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-04-04T18:29:30.144Z","etag":null,"topics":["classification-model","downtrend","drawdown","drawdown-at-risk","finance","machine-learning","maximum-drawdown-at-risk","python","quant","quantitative-finance","time-series","time-series-analysis","time-under-water","timeseries","timeunderwater","trends-detected","uptrend"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rafa-rod.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-05-17T01:17:03.000Z","updated_at":"2026-04-04T16:00:43.000Z","dependencies_parsed_at":"2024-11-15T10:33:05.685Z","dependency_job_id":"69a58f41-ca42-439e-b4d6-cc9eba1fa385","html_url":"https://github.com/rafa-rod/pytrendseries","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rafa-rod/pytrendseries","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafa-rod%2Fpytrendseries","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafa-rod%2Fpytrendseries/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafa-rod%2Fpytrendseries/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafa-rod%2Fpytrendseries/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rafa-rod","download_url":"https://codeload.github.com/rafa-rod/pytrendseries/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafa-rod%2Fpytrendseries/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31463014,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"online","status_checked_at":"2026-04-06T02:00:07.287Z","response_time":112,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["classification-model","downtrend","drawdown","drawdown-at-risk","finance","machine-learning","maximum-drawdown-at-risk","python","quant","quantitative-finance","time-series","time-series-analysis","time-under-water","timeseries","timeunderwater","trends-detected","uptrend"],"created_at":"2024-08-03T04:00:46.740Z","updated_at":"2026-04-06T06:34:37.388Z","avatar_url":"https://github.com/rafa-rod.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":["Trading \u0026 Backtesting"],"readme":"\u003c!-- buttons --\u003e\r\n\u003cp align=\"center\"\u003e\r\n    \u003ca href=\"https://www.linkedin.com/in/rafaelrod/\"\u003e\r\n        \u003cimg src=\"https://img.shields.io/badge/LinkedIn-0077B5?style=flat\u0026logo=linkedin\u0026logoColor=white\"\r\n            alt=\"LinkedIn\"\u003e\u003c/a\u003e \u0026nbsp;\r\n    \u003ca href=\"https://www.python.org/\"\u003e\r\n        \u003cimg src=\"https://img.shields.io/badge/python-v3-brightgreen.svg\"\r\n            alt=\"python\"\u003e\u003c/a\u003e \u0026nbsp;\r\n    \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\r\n        \u003cimg src=\"https://img.shields.io/badge/license-MIT-brightgreen.svg\"\r\n            alt=\"MIT license\"\u003e\u003c/a\u003e \u0026nbsp;\r\n      \u003ca href=\"https://codecov.io/gh/rafa-rod/detecttrend\"\u003e\r\n        \u003cimg src=\"https://codecov.io/gh/rafa-rod/detecttrend/branch/main/graph/badge.svg?token=98EMCTZTOY\"/\u003e\r\n      \u003c/a\u003e\r\n    \u003ca href=\"https://pepy.tech/projects/pytrendseries\"\u003e\r\n        \u003cimg src=\"https://static.pepy.tech/badge/pytrendseries\" alt=\"PyPI Downloads\"\u003e\r\n    \u003c/a\u003e\r\n\u003c/p\u003e\r\n\r\n\u003c!-- content --\u003e\r\n\r\n**pytrendseries** is a Python library for detection of trends in time series like: stock prices, monthly sales, daily temperature of a city and so on.\r\nThe input data must be a `pandas.DataFrame` format containing one column as observed data (in float or int format). Follow example below:\r\n\r\n```python\r\nimport pandas as pd\r\ndata = pd.read_csv(\"tests/resource/stock_prices.csv\")\r\nfiltered_data = data[['period','close']].set_index(\"period\")\r\nfiltered_data.columns = ['close_price']\r\nfiltered_data.index = pd.to_datetime(filtered_data.index)\r\nfiltered_data = filtered_data.sort_index()\r\n```\r\n\r\nOnce some trend is identified, **pytrendseries** provides period on trend, drawdown, maximum drawdown (or drawup in case of uptrend) and a plot with all trends found.\r\n\r\n## Installation\r\n\r\n### Using pip\r\n\r\nYou can install using the pip package manager by running:\r\n\r\n```sh\r\npip install pytrendseries\r\n```\r\n\r\nAlternatively, you could install the latest version directly from Github:\r\n\r\n```sh\r\npython -m pip install git+https://github.com/rafa-rod/pytrendseries.git\r\n```\r\n\r\n## Why pytrendseries is important?\r\n\r\nDetection of trends could be used in machine learning algorithms such as classification problems like binary (1 = uptrend, 0 = otherwise) or non-binary classifications (1 = uptrend, -1 = downtrend, 0 = otherwise). Besides that, could be used in prediction problems.\r\n\r\n## Example\r\n\r\nInform:\r\n\r\n- type of trend you desire to investigate =\u003e downtrend or uptrend;\r\n- window or maximum period of a trend (example: 60 days considering 1 day as 1 period);\r\n- the minimum value that represents the number of consecutive days (or another period of time) to be considered a trend (default 5 periods).\r\n\r\n```python\r\nimport pytrendseries\r\n\r\ntrend = \"downtrend\"\r\nwindow = 126 #6 months\r\n\r\ntrends_detected = pytrendseries.detecttrend(filtered_data, trend=trend, window=window)\r\n```\r\n\r\nThe variable `trends_detected` is a dataframe that contains the initial and end date of each trend, the prices of each date, time span of each trend and the drawdown of each trend. Let's see the first five rows of this dataframe:\r\n\r\n```\r\n| Peak Date           | Valley Date         |   Peak   |   Valley |   index_peak  |index_valley |   time_span |   drawdown |\r\n|:--------------------|:--------------------|---------:|---------:|--------------:|------------:|------------:|-----------:|\r\n| 2000-01-03 00:00:00 | 2000-01-31 00:00:00 |  5.90057 |  5.12252 |             0 |          19 |          19 |  0.131859  |\r\n| 2000-03-09 00:00:00 | 2000-04-24 00:00:00 |  6.42701 |  5.02208 |            45 |          76 |          31 |  0.218597  |\r\n| 2000-05-02 00:00:00 | 2000-05-11 00:00:00 |  5.53684 |  5.29352 |            81 |          88 |           7 |  0.0439456 |\r\n| 2000-05-16 00:00:00 | 2000-05-24 00:00:00 |  5.59962 |  5.24807 |            91 |          97 |           6 |  0.0627803 |\r\n| 2000-06-08 00:00:00 | 2000-06-15 00:00:00 |  6.30359 |  6.1646  |           108 |         113 |           5 |  0.0220487 |\r\n```\r\n\r\nThe easiest way to vizualize the trends detected, just call `plot_trend` function.\r\nAll trends detected, with maximum window informed and the minimum informed by the limit value, will be displayed.\r\n\r\n```python\r\nimport pytrendseries\r\ntrend = \"downtrend\"\r\nwindow = 30\r\nyear = 2020\r\n\r\ntrends_detected = pytrendseries.detecttrend(filtered_data, trend=trend, window=window)\r\npytrendseries.vizplot.plot_trend(filtered_data, trends_detected, trend, year)\r\n```\r\n\r\n\u003ccenter\u003e\r\n\u003cimg src=\"https://github.com/rafa-rod/pytrendseries/blob/main/media/plot_downtrend.png\" style=\"width:90%;\"/\u003e\r\n\u003c/center\u003e\r\n\r\nTo visualize all uptrends found, inform `trend='uptrend'`:\r\n\r\n```python\r\nimport pytrendseries\r\nwindow = 30\r\nyear = 2020\r\n\r\ntrends_detected = pytrendseries.detecttrend(filtered_data, trend='uptrend', window=window)\r\npytrendseries.vizplot.plot_trend(filtered_data, trends_detected, 'uptrend', year)\r\n```\r\n\r\n \u003ccenter\u003e\r\n\u003cimg src=\"https://github.com/rafa-rod/pytrendseries/blob/main/media/plot_uptrend.png\" style=\"width:90%;\"/\u003e\r\n\u003c/center\u003e\r\n\r\n## Maximum Drawdown\r\n\r\nThe `maxdrawdown` calculates the Maximum Drawdown within non-overlapping time windows, providing detailed information about each significant drawdown event.\r\n\r\nThe maximum drawdown or maximum drawup can be obtained by sorting the dataframe by column drawdown. To do that, just code:\r\n\r\n```python\r\nmaxdd_in_window = trends_detected.sort_values(\"drawdown\", ascending=False).iloc[0:1]\r\n```\r\n\r\nAnother way is to call the function `maxdrawdown`. Note that this result will be differente once the maximum drawdown of the intire timeseries, unless you pass same window parameter.\r\n\r\n```python\r\nmaxdd = pytrendseries.maxdrawdown(filtered_data, window=None)\r\n```\r\n\r\nOutput includes:\r\n\r\n- Window Start/End dates;\r\n- Peak and Valley dates with corresponding prices;\r\n- Maximum Drawdown percentage;\r\n- Time Span (number of periods from peak to valley).\r\n\r\nYou can code to vizualize as follows:\r\n\r\n```python\r\nimport matplotlib.pyplot as plt\r\n\r\nplt.figure(figsize=(14,5))\r\nplt.plot(filtered_data, alpha=0.6)\r\nlocation_x = maxdd.values[:,0]\r\nlocation_y = maxdd.values[:,1]\r\nfor i in range(location_x.shape[0]):\r\n    plt.axvspan(location_x[i], location_y[i], alpha=0.3, color=\"red\")\r\nplt.grid(axis='x')\r\nplt.show()\r\n```\r\n\r\n\u003ccenter\u003e\r\n\u003cimg src=\"https://github.com/rafa-rod/pytrendseries/blob/main/media/maxdd.png\" style=\"width:90%;\"/\u003e\r\n\u003c/center\u003e\r\n\r\nYou may pass the parameter window to obtain the same result:\r\n\r\n```python\r\nmaxdd_in_window = pytrendseries.maxdrawdown(filtered_data, window=252)\r\n```\r\n\r\nTo vizualize all drawdowns of timeseries, call the following function:\r\n\r\n```python\r\nimport pytrendseries\r\npytrendseries.plot_drawdowns(filtered_data, figsize = (10,4), color=\"gray\", alpha=0.6, title=\"Drawdowns\", axis=\"y\")\r\n```\r\n\r\n\u003ccenter\u003e\r\n\u003cimg src=\"https://github.com/rafa-rod/pytrendseries/blob/main/media/plot_drawdons.png\" style=\"width:90%;\"/\u003e\r\n\u003c/center\u003e\r\n\r\nAnother option is:\r\n\r\n```python\r\nimport pytrendseries\r\npytrendseries.plot_evolution(filtered_data, figsize = (10,4), colors=[\"gray\", \"red\"], alphas=[1,0.6])\r\n```\r\n\r\n\u003ccenter\u003e\r\n\u003cimg src=\"https://github.com/rafa-rod/pytrendseries/blob/main/media/plot_evolution.png\" style=\"width:90%;\"/\u003e\r\n\u003c/center\u003e\r\n\r\n## Current Drawdown\r\n\r\nThe `calculate_current_drawdown` function returns key metrics including the current price, last peak value and date, current drawdown percentage, and the duration (in number of records) since the last peak.\r\n\r\n```python\r\nimport pytrendseries\r\npytrendseries.calculate_current_drawdown(filtered_data)\r\n```\r\n\r\nOutput example:\r\n\r\n| current_price | last_peak | current_drawdown | last_peak_date | time_in_drawdown |\r\n| ------------: | --------: | ---------------: | -------------: | ---------------: |\r\n|         98.50 |    105.30 |           -6.46% |     2024-01-15 |               12 |\r\n\r\n## Time Under Water (tuw)\r\n\r\nTo get time underwater (tuw), just type:\r\n\r\n```python\r\nimport pytrendseries\r\npytrendseries.calculate_time_under_water(filtered_data)\r\n```\r\n\r\nThe output would be (showing the tail of the dataframe):\r\n\r\n```\r\n| Peak Date | Recovery Date |   Peak   |   Valley  |   MaxDD   | Time Underwater |   Status   |\r\n|:----------|:--------------|---------:|----------:|----------:|----------------:|-----------:|\r\n| 2007-12-28|   2008-05-06  | 44.66140 | 33.58194  |   0.24808 |       85        | Recovered  |\r\n| 2008-05-06|   2008-05-09  | 45.00000 | 44.85000  |   0.00333 |        4        | Recovered  |\r\n| 2008-05-13|   2008-05-15  | 46.95000 | 46.30000  |   0.01384 |        3        | Recovered  |\r\n| 2008-05-21|      NaT      | 52.51000 |  4.20000  |   0.92002 |      235        |  Ongoing   |\r\n```\r\n\r\nThe _Status_ column indicates whether the drawdown period has recovered or is still ongoing, while Time Underwater shows the number of periods spent below the previous peak.\r\n\r\nAnother important usage of `pytrendseries` is to obtain the series of drawdowns or series of maximum drawdowns in order to calculate the drawdown at risk or maximum drawdown at risk.\r\n\r\n```python\r\nimport pytrendseries\r\nimport matplotlib.pyplot as plt\r\nimport seaborn as sns; sns.set_style(\"white\")\r\n\r\ntrend = \"downtrend\"\r\nwindow = 126 #6 months\r\n\r\ntrends_detected = pytrendseries.detecttrend(filtered_data, trend=trend, window=window)\r\n\r\nplt.figure(figsize=(15,5))\r\nsns.histplot(trends_detected[\"drawdown\"]*100, kde=True, bins=30)\r\nplt.ylabel(\"\")\r\nplt.box(False)\r\nplt.annotate('Maximum Drawdown', xy=((trends_detected[\"drawdown\"].max()-0.005)*100, 1),\r\n             xycoords='data',\r\n            xytext=(-105, 30), textcoords='offset points',color=\"red\",\r\n            weight='bold',\r\n            arrowprops=dict(arrowstyle=\"-\u003e\", color=\"r\",\r\n                            connectionstyle='arc3,rad=-0.1'))\r\nplt.annotate('Quantile 97,5%', xy=((trends_detected[\"drawdown\"].quantile(0.975)-0.005)*100, 0.2),\r\n             xycoords='data',\r\n            xytext=(-135, 30), textcoords='offset points',color=\"red\",\r\n            weight='bold',\r\n            arrowprops=dict(arrowstyle=\"-\u003e\", color=\"r\",\r\n                            connectionstyle='arc3,rad=-0.1'))\r\nplt.xlabel(\"Drawdown (%)\")\r\nplt.ylabel(\"Density\", rotation=0, labelpad=-30, loc=\"top\")\r\nplt.show()\r\n```\r\n\r\n\u003ccenter\u003e\r\n\u003cimg src=\"https://github.com/rafa-rod/pytrendseries/blob/main/media/series_drawdown.png\" style=\"width:90%;\"/\u003e\r\n\u003c/center\u003e\r\n\r\n```python\r\nimport pytrendseries\r\nimport matplotlib.pyplot as plt\r\nimport seaborn as sns; sns.set_style(\"white\")\r\n\r\nmaxdd_in_window = pytrendseries.maxdrawdown(filtered_data, window=126)\r\n\r\nplt.figure(figsize=(15,5))\r\nsns.histplot(maxdd_in_window[\"MaxDD\"]*100, kde=True, bins=30)\r\nplt.ylabel(\"\")\r\nplt.box(False)\r\nplt.annotate('Maximum Drawdown', xy=((maxdd_in_window[\"MaxDD\"].max()-0.005)*100, 1),\r\n             xycoords='data',\r\n            xytext=(-105, 30), textcoords='offset points',color=\"red\",\r\n            weight='bold',\r\n            arrowprops=dict(arrowstyle=\"-\u003e\", color=\"r\",\r\n                            connectionstyle='arc3,rad=-0.1'))\r\nplt.annotate('Quantile 95%', xy=((maxdd_in_window[\"MaxDD\"].quantile(0.95)-0.005)*100, 0.2),\r\n             xycoords='data',\r\n            xytext=(-135, 50), textcoords='offset points',color=\"red\",\r\n            weight='bold',\r\n            arrowprops=dict(arrowstyle=\"-\u003e\", color=\"r\",\r\n                            connectionstyle='arc3,rad=-0.1'))\r\nplt.xlabel(\"Maximum Drawdowns (%)\")\r\nplt.ylabel(\"Density\", rotation=0, labelpad=-30, loc=\"top\")\r\nplt.show()\r\n```\r\n\r\n\u003ccenter\u003e\r\n\u003cimg src=\"https://github.com/rafa-rod/pytrendseries/blob/main/media/series_max_drawdown.png\" style=\"width:90%;\"/\u003e\r\n\u003c/center\u003e\r\n\r\n## Trend Labeling for Machine Learning\r\n\r\nThe `get_trends_labels` function automates the process of labeling financial time series data based on detected market structures. By identifying peaks and valleys within a specified window, it segments the data into **Uptrends**, **Downtrends**, and **No Trend** periods.\r\n\r\n## Why use this for Machine Learning?\r\n\r\nThis function is particularly useful for **supervised learning classification problems**. Instead of trying to predict the exact future price (a regression problem), you can train models to predict the market state or direction.\r\n\r\n- **Target Variable Engineering:** Converts raw price data into discrete classes (e.g., `1`, `-1`, `0`).\r\n- **Noise Reduction:** Ignores minor fluctuations by focusing on significant trends defined by the `window` and `limit` parameters.\r\n- **Flexibility:** Allows custom labeling schemes (e.g., numeric for models, strings for interpretability).\r\n\r\n## Output Example\r\n\r\nAfter running the function, your dataframe will include a new `label` column indicating the market regime for each date.\r\n\r\n| Date       | Close | Label  | Market State  |\r\n| :--------- | :---- | :----: | :------------ |\r\n| 2023-01-03 | 100.5 |   0    | No Trend      |\r\n| 2023-01-04 | 101.2 |   0    | No Trend      |\r\n| 2023-01-05 | 103.5 | **1**  | **Uptrend**   |\r\n| 2023-01-06 | 105.0 | **1**  | **Uptrend**   |\r\n| 2023-01-09 | 104.8 | **1**  | **Uptrend**   |\r\n| 2023-01-10 | 102.0 |   0    | No Trend      |\r\n| 2023-01-11 | 99.5  | **-1** | **Downtrend** |\r\n| 2023-01-12 | 98.0  | **-1** | **Downtrend** |\r\n\r\n## Usage Examples\r\n\r\n### 1. Default Configuration\r\n\r\nUses standard numeric labels (`1` for uptrend, `-1` for downtrend, `0` for no trend).\r\n\r\n```python\r\nimport pytrendseries\r\ndf_labeled = pytrendseries.get_trends_labels(df, window=252, limit=5)\r\n```\r\n\r\n### 2. Custom String Labels\r\n\r\nUseful for interpretability or specific model requirements.\r\n\r\n```python\r\ncustom_labels = {\r\n    \"uptrend\": \"BUY\",\r\n    \"downtrend\": \"SELL\",\r\n    \"notrend\": \"HOLD\"\r\n}\r\ndf_labeled = pytrendseries.get_trends_labels(df, labels=custom_labels)\r\n```\r\n\r\n### 3. Binary Classification (Uptrend vs. Rest)\r\n\r\nIgnore downtrends and treat them as \"no trend\" for a specific strategy.\r\n\r\n```python\r\nbinary_labels = {\r\n    \"uptrend\": 1,\r\n    \"notrend\": 0\r\n}\r\ndf_labeled = pytrendseries.get_trends_labels(df, labels=binary_labels)\r\n```\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafa-rod%2Fpytrendseries","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frafa-rod%2Fpytrendseries","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafa-rod%2Fpytrendseries/lists"}