first commit
This commit is contained in:
25
src/exchanges/base.py
Normal file
25
src/exchanges/base.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import abc
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any
|
||||
|
||||
class Trade:
|
||||
def __init__(self, exchange: str, symbol: str, price: float, quantity: float, timestamp: datetime, isin: str = None):
|
||||
self.exchange = exchange
|
||||
self.symbol = symbol
|
||||
self.isin = isin
|
||||
self.price = price
|
||||
self.quantity = quantity
|
||||
self.timestamp = timestamp
|
||||
|
||||
def __repr__(self):
|
||||
return f"Trade({self.exchange}, {self.symbol}, {self.price}, {self.quantity}, {self.timestamp})"
|
||||
|
||||
class BaseExchange(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
def fetch_latest_trades(self) -> List[Trade]:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def name(self) -> str:
|
||||
pass
|
||||
75
src/exchanges/eix.py
Normal file
75
src/exchanges/eix.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import requests
|
||||
import json
|
||||
from bs4 import BeautifulSoup
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
from .base import BaseExchange, Trade
|
||||
import csv
|
||||
import io
|
||||
|
||||
class EIXExchange(BaseExchange):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "EIX"
|
||||
|
||||
def fetch_latest_trades(self) -> List[Trade]:
|
||||
url = "https://european-investor-exchange.com/en/trade-list"
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
next_data_script = soup.find('script', id='__NEXT_DATA__')
|
||||
if not next_data_script:
|
||||
return []
|
||||
|
||||
data = json.loads(next_data_script.string)
|
||||
# The structure according to subagent: data['props']['pageProps']['rowsData']
|
||||
rows_data = data.get('props', {}).get('pageProps', {}).get('rowsData', [])
|
||||
|
||||
trades = []
|
||||
for row in rows_data:
|
||||
# We only want the most recent ones. For simplicity, let's pick the first one which is likely the latest.
|
||||
# In a real daemon, we might want to track which ones we already processed.
|
||||
file_key = row.get('key')
|
||||
if not file_key:
|
||||
continue
|
||||
|
||||
# Download the CSV
|
||||
csv_url = f"https://european-investor-exchange.com/api/trade-file-contents?key={file_key}"
|
||||
csv_response = requests.get(csv_url)
|
||||
if csv_response.status_code == 200:
|
||||
trades.extend(self._parse_csv(csv_response.text))
|
||||
# Break after one file for demonstration or handle multiple
|
||||
break
|
||||
|
||||
return trades
|
||||
|
||||
def _parse_csv(self, csv_text: str) -> List[Trade]:
|
||||
trades = []
|
||||
f = io.StringIO(csv_text)
|
||||
# Header: Trading day & Trading time UTC,Instrument Identifier,Quantity,Unit Price,Price Currency,Venue Identifier,Side
|
||||
reader = csv.DictReader(f, delimiter=',')
|
||||
for row in reader:
|
||||
try:
|
||||
price = float(row['Unit Price'])
|
||||
quantity = float(row['Quantity'])
|
||||
isin = row['Instrument Identifier']
|
||||
symbol = isin # Often symbol is unknown, use ISIN
|
||||
time_str = row['Trading day & Trading time UTC']
|
||||
|
||||
# Format: 2026-01-22T06:30:00.617Z
|
||||
# Python 3.11+ supports ISO with Z, otherwise we strip Z
|
||||
ts_str = time_str.replace('Z', '+00:00')
|
||||
timestamp = datetime.fromisoformat(ts_str)
|
||||
|
||||
trades.append(Trade(
|
||||
exchange=self.name,
|
||||
symbol=symbol,
|
||||
isin=isin,
|
||||
price=price,
|
||||
quantity=quantity,
|
||||
timestamp=timestamp
|
||||
))
|
||||
except Exception:
|
||||
continue
|
||||
return trades
|
||||
58
src/exchanges/ls.py
Normal file
58
src/exchanges/ls.py
Normal file
@@ -0,0 +1,58 @@
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
from .base import BaseExchange, Trade
|
||||
|
||||
class LSExchange(BaseExchange):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "LS"
|
||||
|
||||
def fetch_latest_trades(self) -> List[Trade]:
|
||||
# Today's trades endpoint
|
||||
url = "https://www.ls-x.de/_rpc/json/.lstc/instrument/list/lstctradestoday"
|
||||
|
||||
# We might need headers to mimic a browser or handle disclaimer
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Accept': 'application/json',
|
||||
'Referer': 'https://www.ls-tc.de/'
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
|
||||
import csv
|
||||
import io
|
||||
f = io.StringIO(response.text)
|
||||
# Header: isin;displayName;tradeTime;price;currency;size;orderId
|
||||
reader = csv.DictReader(f, delimiter=';')
|
||||
|
||||
trades = []
|
||||
for item in reader:
|
||||
try:
|
||||
price = float(item['price'].replace(',', '.'))
|
||||
quantity = float(item['size'].replace(',', '.'))
|
||||
isin = item['isin']
|
||||
symbol = item['displayName']
|
||||
time_str = item['tradeTime']
|
||||
|
||||
# Format: 2026-01-23T07:30:00.992000Z
|
||||
ts_str = time_str.replace('Z', '+00:00')
|
||||
timestamp = datetime.fromisoformat(ts_str)
|
||||
|
||||
trades.append(Trade(
|
||||
exchange=self.name,
|
||||
symbol=symbol,
|
||||
isin=isin,
|
||||
price=price,
|
||||
quantity=quantity,
|
||||
timestamp=timestamp
|
||||
))
|
||||
except Exception:
|
||||
continue
|
||||
return trades
|
||||
except Exception as e:
|
||||
print(f"Error fetching LS data: {e}")
|
||||
return []
|
||||
Reference in New Issue
Block a user