Skip to content

Commit

Permalink
add asyncio support (#15)
Browse files Browse the repository at this point in the history
- Add asyncio support.
- Fix day_details old shareholders list.
- Fix shareholders change comma bug.
- Implement the get raw stats method.
  • Loading branch information
mahs4d committed Jun 14, 2023
1 parent 72ab172 commit 7b2402a
Show file tree
Hide file tree
Showing 15 changed files with 317 additions and 16 deletions.
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ You can install this library using the following command:

You can find examples of using each component in `examples` directory.

| Component | Example File |
|--------------|-------------------------------------------------------------|
| Symbol | [symbol_example.py](examples/symbol_example.py) |
| Market Watch | [market_watch_example.py](examples/market_watch_example.py) |
| Day Details | [day_details_example.py](examples/day_details_example.py) |
| Market Map | [market_map_example.py](examples/market_map_example.py) |
| Group | [group_example.py](examples/group_example.py) |
| Component | Example File |
|--------------|--------------------------------------------------------------------------------------------------------------|
| Symbol | [symbol_example.py](https://github.com/mahs4d/tsetmc-api/blob/master/examples/symbol_example.py) |
| Market Watch | [market_watch_example.py](https://github.com/mahs4d/tsetmc-api/blob/master/examples/market_watch_example.py) |
| Day Details | [day_details_example.py](https://github.com/mahs4d/tsetmc-api/blob/master/examples/day_details_example.py) |
| Market Map | [market_map_example.py](https://github.com/mahs4d/tsetmc-api/blob/master/examples/market_map_example.py) |
| Group | [group_example.py](https://github.com/mahs4d/tsetmc-api/blob/master/examples/group_example.py) |
| Async | [async_example.py](https://github.com/mahs4d/tsetmc-api/blob/master/examples/async_example.py) |

## Usage

Expand Down Expand Up @@ -54,14 +55,19 @@ Group component currently only has one function (`get_all_groups`) which returns

Tsetmc sometimes returns 403 and you should retry.

### Async Support

Each method in the library has an async counterpart with the same name and a `_async` suffix.
These methods use the sync functions behind the scene in an asyncio pool executor.

### TODO

- [ ] Migrate `symbol` component to use new tsetmc.
- [ ] Migrate `market_watch` component to use new tsetmc.
- [x] Migrate `day_details` component to use new tsetmc.
- [x] Migrate `market_map` component to use new tsetmc.
- [x] Migrate `group` component to use new tsetmc.
- [ ] Support asyncio.
- [x] Support asyncio.

## Support and Donation

Expand Down
21 changes: 21 additions & 0 deletions examples/async_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import asyncio

from tsetmc_api.symbol import Symbol


async def main():
# دیدن مشخصات یک نماد (نماد آسیاتک)
# این عددی که به عنوان symbol_id بهش میدیم، از تیکه‌ی آخر url صفحه‌ی آسیاتک توی سایت برداشته شده
# مثلا آدرس آسیاتک توی tsetmc اینه: http://main.tsetmc.com/InstInfo/14079693677610396
# اون تیکه آخرش میشه symbol_id
symbol = Symbol(symbol_id='14079693677610396')

# اطلاعات قیمتی داخل در یک نگاه
# نام تابع دقیقا همان نام تابع sync است با یک پسوند _async
price_overview = await symbol.get_price_overview_async()
print(price_overview)


if __name__ == '__main__':
loop = asyncio.new_event_loop()
loop.run_until_complete(main())
2 changes: 1 addition & 1 deletion lib/tsetmc_api/day_details/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ def get_day_details_shareholders_data(symbol_id: str, date: jdate) -> tuple[list
'shares_percentage': row['perOfShares'],
}

if row['dEven'] < it:
if row['dEven'] <= it:
old_shareholders.append(sh_data)
else:
new_shareholders.append(sh_data)
Expand Down
76 changes: 74 additions & 2 deletions lib/tsetmc_api/day_details/day_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .threshold import DayDetailsThresholdsData
from .trade import DayDetailsTradeDataRow
from .traders_type import DayDetailsTradersTypeData, DayDetailsTradersTypeInfo, DayDetailsTradersTypeSubInfo
from ..utils import run_sync_function


class DayDetails:
Expand Down Expand Up @@ -34,6 +35,15 @@ def get_price_overview(self) -> DayDetailsPriceOverview:
value=raw_data['value'],
)

async def get_price_overview_async(self) -> DayDetailsPriceOverview:
"""
returns an overview of price information for that day
"""

return await run_sync_function(
func=self.get_price_overview,
)

def get_price_data(self) -> list[DayDetailsPriceDataRow]:
"""
returns instant prices (for each time in that date)
Expand All @@ -50,6 +60,15 @@ def get_price_data(self) -> list[DayDetailsPriceDataRow]:
count=row['count'],
) for row in raw_data]

async def get_price_data_async(self) -> list[DayDetailsPriceDataRow]:
"""
returns instant prices (for each time in that date)
"""

return await run_sync_function(
func=self.get_price_data,
)

def get_orderbook_data(self) -> list[DayDetailsOrderBookDataRow]:
"""
returns instant orderbooks (for each time in that date)
Expand All @@ -73,6 +92,15 @@ def get_orderbook_data(self) -> list[DayDetailsOrderBookDataRow]:
) for row in data['sell_rows']],
) for data in raw_data]

async def get_orderbook_data_async(self) -> list[DayDetailsOrderBookDataRow]:
"""
returns instant orderbooks (for each time in that date)
"""

return await run_sync_function(
func=self.get_orderbook_data,
)

def get_traders_type_data(self) -> DayDetailsTradersTypeData:
"""
returns traders type information for that day
Expand Down Expand Up @@ -107,6 +135,15 @@ def get_traders_type_data(self) -> DayDetailsTradersTypeData:
),
)

async def get_traders_type_data_async(self) -> DayDetailsTradersTypeData:
"""
returns traders type information for that day
"""

return await run_sync_function(
func=self.get_traders_type_data,
)

def get_trades_data(self, summarize: bool = False) -> list[DayDetailsTradeDataRow]:
"""
gets all trade data
Expand All @@ -120,7 +157,21 @@ def get_trades_data(self, summarize: bool = False) -> list[DayDetailsTradeDataRo
volume=row['volume'],
) for row in raw_data]

async def get_trades_data_async(self, summarize: bool = False) -> list[DayDetailsTradeDataRow]:
"""
gets all trade data
"""

return await run_sync_function(
func=self.get_trades_data,
summarize=summarize,
)

def get_thresholds_data(self) -> list[DayDetailsThresholdsData]:
"""
Get allowed price window
"""

raw_data = _core.get_day_details_thresholds_data(symbol_id=self.symbol_id, date=self.date)

return [DayDetailsThresholdsData(
Expand All @@ -129,6 +180,15 @@ def get_thresholds_data(self) -> list[DayDetailsThresholdsData]:
range_min=row['min'],
) for row in raw_data]

async def get_thresholds_data_async(self) -> list[DayDetailsThresholdsData]:
"""
Get allowed price window
"""

return await run_sync_function(
func=self.get_thresholds_data,
)

def get_shareholders_data(self) -> tuple[list[DayDetailsShareHolderDataRow], list[DayDetailsShareHolderDataRow]]:
"""
gets list of shareholders before and after the day
Expand All @@ -143,8 +203,8 @@ def get_shareholders_data(self) -> tuple[list[DayDetailsShareHolderDataRow], lis
symbol_id=self.symbol_id,
date=self.date,
shareholder=DayDetailsShareHolder(id=row['id'], name=row['name']),
count=row['count'],
percentage=row['percentage'],
shares_count=row['shares_count'],
shares_percentage=row['shares_percentage'],
) for row in raw_old_shareholders]

new_shareholders = [DayDetailsShareHolderDataRow(
Expand All @@ -156,3 +216,15 @@ def get_shareholders_data(self) -> tuple[list[DayDetailsShareHolderDataRow], lis
) for row in raw_new_shareholders]

return old_shareholders, new_shareholders

async def get_shareholders_data_async(self) -> tuple[
list[DayDetailsShareHolderDataRow],
list[DayDetailsShareHolderDataRow],
]:
"""
gets list of shareholders before and after the day
"""

return await run_sync_function(
func=self.get_shareholders_data,
)
20 changes: 20 additions & 0 deletions lib/tsetmc_api/day_details/shareholder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pydantic import BaseModel

from . import _core
from ..utils import run_sync_function


class DayDetailsShareHolderPortfolioRow(BaseModel):
Expand Down Expand Up @@ -31,6 +32,15 @@ def get_portfolio_data(self) -> list[DayDetailsShareHolderPortfolioRow]:
shares_percent=row['shares_percent'],
) for row in raw_data]

async def get_portfolio_data_async(self) -> list[DayDetailsShareHolderPortfolioRow]:
"""
returns list of companies owned by this shareholder
"""

return await run_sync_function(
func=self.get_portfolio_data,
)


class DayDetailsShareHolderChartRow(BaseModel):
date: jdate
Expand Down Expand Up @@ -65,5 +75,15 @@ def get_chart_data(self, days: int = 90) -> list[DayDetailsShareHolderChartRow]:
shares_percentage=row['shares_percentage'],
) for row in raw_data]

async def get_chart_data_async(self, days: int = 90) -> list[DayDetailsShareHolderChartRow]:
"""
returns list of changes to shareholders share count in history of this symbol
"""

return await run_sync_function(
func=self.get_chart_data_async,
days=days,
)

class Config:
arbitrary_types_allowed = True
11 changes: 11 additions & 0 deletions lib/tsetmc_api/group/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pydantic import BaseModel

from . import _core
from ..utils import run_sync_function


class GroupType(Enum):
Expand Down Expand Up @@ -33,3 +34,13 @@ def get_all_groups() -> list[Group]:
description=row['description'],
type=GroupType.PAPER if row['type'] == 'PaperType' else GroupType.INDUSTRIAL,
) for row in raw_data]

@staticmethod
async def get_all_groups_async() -> list[Group]:
"""
returns list of symbol groups
"""

return await run_sync_function(
func=Group.get_all_groups,
)
12 changes: 12 additions & 0 deletions lib/tsetmc_api/market_map/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pydantic.utils import deep_update

from . import _core
from ..utils import run_sync_function


class MapDataRow(BaseModel):
Expand Down Expand Up @@ -64,3 +65,14 @@ def get_market_map_data(self, map_type: MapType = MapType.MARKET_VALUE) -> dict[
) for key, data in raw_data.items()}

return map_data

async def get_market_map_data_async(self, map_type: MapType = MapType.MARKET_VALUE) -> dict[str, MapDataRow]:
"""
returns symbol data in market map (in "naghshe bazar" page)
!!! webserver occasionally throws 403 error, you should retry in a few seconds when this happens
"""

return await run_sync_function(
func=self.get_market_map_data,
map_type=map_type,
)
2 changes: 1 addition & 1 deletion lib/tsetmc_api/market_watch/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ def get_watch_traders_type_data() -> dict:

def get_watch_daily_history_data() -> dict:
response = requests.get(
url='http://members.tsetmc.com/tsev2/data/ClosingPriceAll.aspx',
url='https://members.tsetmc.com/tsev2/data/ClosingPriceAll.aspx',
params={},
verify=False,
timeout=20,
Expand Down
49 changes: 48 additions & 1 deletion lib/tsetmc_api/market_watch/watch.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .orderbook import WatchOrderBook, WatchOrderBookRow
from .price import WatchPriceDataRow
from .traders_type import WatchTradersTypeDataRow, WatchTradersTypeInfo, WatchTradersTypeSubInfo
from ..utils import deep_update
from ..utils import deep_update, run_sync_function


class MarketWatch:
Expand Down Expand Up @@ -71,6 +71,15 @@ def get_price_data(self) -> dict[str, WatchPriceDataRow]:

return watch_data

async def get_price_data_async(self) -> dict[str, WatchPriceDataRow]:
"""
gets basic price information (in "didbane bazar" page)
"""

return await run_sync_function(
func=self.get_price_data,
)

def get_traders_type_data(self) -> dict[str, WatchTradersTypeDataRow]:
"""
gets traders type data (in "didebane bazar" page)
Expand Down Expand Up @@ -105,6 +114,15 @@ def get_traders_type_data(self) -> dict[str, WatchTradersTypeDataRow]:

return watch_data

async def get_traders_type_data_async(self) -> dict[str, WatchTradersTypeDataRow]:
"""
gets traders type data (in "didebane bazar" page)
"""

return await run_sync_function(
func=self.get_traders_type_data,
)

def get_daily_history_data(self) -> dict[str, list[WatchDailyHistoryDataRow]]:
"""
gets 30 day history of symbols (in "didbane bazar" page)
Expand All @@ -129,14 +147,43 @@ def get_daily_history_data(self) -> dict[str, list[WatchDailyHistoryDataRow]]:

return watch_data

async def get_daily_history_data_async(self) -> dict[str, list[WatchDailyHistoryDataRow]]:
"""
gets 30 day history of symbols (in "didbane bazar" page)
"""

return await run_sync_function(
func=self.get_daily_history_data,
)

def get_raw_stats_data(self) -> dict[list]:
"""
returns a list of stats for each symbol. refer to tsetmc.com for information of what each item in the list is
"""

return _core.get_watch_raw_stats_data()

async def get_raw_stats_data_async(self) -> dict[list]:
"""
returns a list of stats for each symbol. refer to tsetmc.com for information of what each item in the list is
"""

return await run_sync_function(
func=self.get_raw_stats_data,
)

def get_stats_data(self) -> dict[dict]:
"""
!!! EXPERIMENTAL: returns stats in dict, may be wrong !!!
"""

return _core.get_watch_stats_data()

async def get_stats_data_async(self) -> dict[dict]:
"""
!!! EXPERIMENTAL: returns stats in dict, may be wrong !!!
"""

return await run_sync_function(
func=self.get_stats_data,
)
2 changes: 1 addition & 1 deletion lib/tsetmc_api/symbol/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ def get_symbol_shareholders(company_isin: str) -> list[dict]:
name = tds[0].text.strip()
count = int(tds[1].div['title'].replace(',', ''))
percentage = float(tds[2].text)
change = locale.atoi(tds[3].text.strip())
change = locale.atoi(tds[3].text.replace(',', '').strip())

shareholders.append({
'id': shareholder_id,
Expand Down
Loading

0 comments on commit 7b2402a

Please sign in to comment.