|
|
@@ -1,6 +1,7 @@
|
|
|
from __future__ import annotations
|
|
|
|
|
|
import collections
|
|
|
+import concurrent.futures
|
|
|
import dataclasses
|
|
|
import datetime
|
|
|
import statistics
|
|
|
@@ -24,27 +25,20 @@ def main() -> None:
|
|
|
check_cxos()
|
|
|
|
|
|
markets: dict[str, list[Market]] = collections.defaultdict(list)
|
|
|
- for price in raw_prices:
|
|
|
- if (traded := price['AverageTraded7D']) is None or traded < 100:
|
|
|
- continue
|
|
|
- if price['Bid'] is None or price['Ask'] is None:
|
|
|
- continue
|
|
|
- if (high := price['HighYesterday']) is None or (low := price['LowYesterday']) is None:
|
|
|
- continue
|
|
|
- if (high - low) / high < 0.1:
|
|
|
- continue
|
|
|
- spread = (price['Ask'] - price['Bid']) / price['Ask']
|
|
|
- if spread < 0.15:
|
|
|
- continue
|
|
|
- chart_analysis = analyze_price_chart(price['FullTicker'], (price['Bid'] + price['Ask']) / 2)
|
|
|
- markets[price['ExchangeCode']].append(Market(price['FullTicker'], bid=price['Bid'], ask=price['Ask'],
|
|
|
- spread=spread, chart_analysis=chart_analysis))
|
|
|
+ with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
|
|
+ futures: list[concurrent.futures.Future[Market | None]] = []
|
|
|
+ for price in raw_prices:
|
|
|
+ futures.append(executor.submit(analyze_raw_price, price))
|
|
|
+ for future in futures:
|
|
|
+ if (market := future.result()) is not None:
|
|
|
+ markets[market.exchange_code].append(market)
|
|
|
+ executor.shutdown()
|
|
|
|
|
|
print(' mat bid ask spread bids filled asks filled profit p75 fill time')
|
|
|
for commodities in markets.values():
|
|
|
commodities.sort(reverse=True)
|
|
|
for m in commodities:
|
|
|
- print(f'{m.full_ticker:>8} {m.bid:5} {m.ask:5} {m.spread*100: 5.0f}% {m.chart_analysis.bids_filled:12.0f} '
|
|
|
+ print(f'{m.ticker:>4}.{m.exchange_code} {m.bid:5} {m.ask:5} {m.spread*100: 5.0f}% {m.chart_analysis.bids_filled:12.0f} '
|
|
|
f'{m.chart_analysis.asks_filled:12.0f} {m.chart_analysis.profits:10.0f} {format_td(m.chart_analysis.p75_fill_time)}')
|
|
|
print()
|
|
|
|
|
|
@@ -76,6 +70,22 @@ def check_cxos() -> None:
|
|
|
print('warehouse', warehouse['LocationNaturalId'], 'is not empty')
|
|
|
print()
|
|
|
|
|
|
+def analyze_raw_price(price: RawPrice) -> Market | None:
|
|
|
+ if (traded := price['AverageTraded7D']) is None or traded < 100:
|
|
|
+ return
|
|
|
+ if price['Bid'] is None or price['Ask'] is None:
|
|
|
+ return
|
|
|
+ if (high := price['HighYesterday']) is None or (low := price['LowYesterday']) is None:
|
|
|
+ return
|
|
|
+ if (high - low) / high < 0.1:
|
|
|
+ return
|
|
|
+ spread = (price['Ask'] - price['Bid']) / price['Ask']
|
|
|
+ if spread < 0.15:
|
|
|
+ return
|
|
|
+ chart_analysis = analyze_price_chart(price['FullTicker'], (price['Bid'] + price['Ask']) / 2)
|
|
|
+ return Market(price['ExchangeCode'], price['MaterialTicker'], bid=price['Bid'], ask=price['Ask'],
|
|
|
+ spread=spread, chart_analysis=chart_analysis)
|
|
|
+
|
|
|
def analyze_price_chart(exchange_ticker: str, midpoint: float) -> ChartAnalysis:
|
|
|
'''use price chart to estimate how long it takes to fill a bid and then an ask'''
|
|
|
pcpoints: list[PriceChartPoint] = [p for p in cache.get('https://rest.fnar.net/exchange/cxpc/' + exchange_ticker)
|
|
|
@@ -201,7 +211,8 @@ class ChartAnalysis:
|
|
|
|
|
|
@dataclasses.dataclass(eq=False, frozen=True, slots=True)
|
|
|
class Market:
|
|
|
- full_ticker: str
|
|
|
+ exchange_code: str
|
|
|
+ ticker: str
|
|
|
bid: float
|
|
|
ask: float
|
|
|
spread: float
|