from bs4 import BeautifulSoup
import requests
import re
import os

########################### DEFINITIONS ###########################

dry_run = False # False to see if what would have ran

my_auth = ("username", r"password")
spring_homepage = "link_to_spring_homepage"

base_link = "https://cate.doc.ic.ac.uk/"
base_path = "/path/to/your/directory"

module_dict = {"Introduction to Computer Architecture": "113 - ",
               "Programming II (Java)": "120.2 - ",
               "Introduction to Databases": "130 - ",
               "Reasoning about Programs": "141 - ",
               "Graphs and Algorithms": "150 - ",
               "Computing Practical 1": "161 - ",
               "Professional Issues": "166 - "}

def href_is_file(href):
    return href and re.compile("^showfile").search(href)

def href_is_answer(href):
    return href and re.compile("^showfile.*MODELS.*").search(href)

def href_is_data(href):
    return href and re.compile("^showfile.*DATA.*").search(href)

########################### FILE HANDLING ###########################

def init_checks():
    if not os.path.isdir(base_path):
        print("Input path does not exist!")
        raise SystemExit

def download_file(name, url, output_path):
    """Downloads file at url to
    output path using requests"""
    if not (dry_run or os.path.isfile(output_path)):
        create_dir_if_not_exist(os.path.dirname(output_path))

        response = requests.get(url, auth=my_auth)
        with open(output_path, 'wb') as file:
            file.write(response.content)
        print("Successfully downloaded: " + name)
    else:
        print("Dry run or file already exists, not downloading file: " + name)

def create_dir_if_not_exist(dir_path):
    if not dry_run:
        if not os.path.exists(dir_path):
            os.makedirs(dir_path)
    else:
        print("Dry run, not creating dir: " + dir_path)

########################### GET NAMES ###########################

def get_file_name(item):
    file_name = item.contents[0].strip()
    file_prefix = item.find_previous_sibling().contents[0]
    file_name = file_prefix + " - " + file_name
    return file_name.replace(':', '_')

def get_module_name(item):
    sibling = item.find_next_sibling()
    while (sibling["href"].find("mailto") == -1):
        sibling = sibling.find_next_sibling()
    module_name = re.compile(r"(.*)'(.*)'(.*)").search(sibling["href"]).group(2)
    return module_name

def get_given_href(item):
    given = item.find_next_sibling()

    if not given:
        return (False, "")
    if not given["href"].startswith("given"):
        return (False, "")
    else:
        return (True, given["href"])

########################### MAIN ###########################

def simple_soup(link):
    html_response = requests.get(link, auth=my_auth).text
    return BeautifulSoup(html_response, features='html.parser')

def get_given_link(link):
    html_response = requests.get(link, auth=my_auth).content
    ans_soup = BeautifulSoup(html_response, features='html.parser')
    ans_dl_href = ans_soup.find('a', href=href_is_answer)
    data_dl_href = ans_soup.find('a', href=href_is_data)

    if ans_dl_href:
        return ("ans", ans_dl_href["href"])
    elif data_dl_href:
        return ("data", data_dl_href["href"])
    else:
        print("Error: no valid href found on external page: " + link)

def main():
    init_checks()
    soup_object = simple_soup(spring_homepage)
    for item in soup_object.find_all('a', href=href_is_file):
        module_name = get_module_name(item)
        download_dir = base_path + module_dict[module_name] + module_name + "/exercises/"

        spec_name =  get_file_name(item)
        spec_dl_link = base_link + item["href"]
        spec_dl_path = download_dir + spec_name + ".pdf"

        (has_ext, ext_href_link) = get_given_href(item)
        if has_ext:
            (ext_type, ext_dl_link) = get_given_link(base_link + ext_href_link)
            ext_dl_link = base_link + ext_dl_link

            if ext_type == "ans":
                ext_dl_path = download_dir + spec_name + "_ANS" + ".pdf"
                download_file(spec_name + "_ANS", ext_dl_link, ext_dl_path)
            elif ext_type == "data":
                ext_dl_path = download_dir + spec_name + "_DATA"
                download_file(spec_name + "_DATA", ext_dl_link, ext_dl_path)

        ##### DOWNLOAD FILES ######
        download_file(spec_name, spec_dl_link, spec_dl_path)

if __name__ == '__main__':
    main()
    print("Done!")


import asyncio
import time
from copy import deepcopy
from decimal import Decimal
from math import ceil
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple

from bidict import bidict
from dateutil.parser import parse as dateparse
from dydx3.helpers.request_helpers import epoch_seconds_to_iso, generate_now_iso, iso_to_epoch_seconds

import hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_constants as CONSTANTS
from hummingbot.connector.constants import s_decimal_0, s_decimal_NaN
from hummingbot.connector.derivative.dydx_perpetual import dydx_perpetual_web_utils as web_utils
from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_api_order_book_data_source import (
    DydxPerpetualAPIOrderBookDataSource,
)
from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_auth import DydxPerpetualAuth
from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_user_stream_data_source import (
    DydxPerpetualUserStreamDataSource,
)
from hummingbot.connector.derivative.dydx_perpetual.dydx_perpetual_utils import clamp
from hummingbot.connector.derivative.position import Position
from hummingbot.connector.perpetual_derivative_py_base import PerpetualDerivativePyBase
from hummingbot.connector.trading_rule import TradingRule
from hummingbot.connector.utils import combine_to_hb_trading_pair
from hummingbot.core.api_throttler.data_types import LinkedLimitWeightPair, RateLimit
from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, TradeType
from hummingbot.core.data_type.in_flight_order import InFlightOrder, OrderState, OrderUpdate, TradeUpdate
from hummingbot.core.data_type.trade_fee import AddedToCostTradeFee, TokenAmount, TradeFeeBase
from hummingbot.core.data_type.user_stream_tracker_data_source import UserStreamTrackerDataSource
from hummingbot.core.event.events import AccountEvent, PositionModeChangeEvent
from hummingbot.core.utils.estimate_fee import build_perpetual_trade_fee
from hummingbot.core.web_assistant.web_assistants_factory import WebAssistantsFactory

if TYPE_CHECKING:
    from hummingbot.client.config.config_helpers import ClientConfigAdapter

class DydxPerpetualDerivative(PerpetualDerivativePyBase):
    web_utils = web_utils

    def __init__(
            self,
            client_config_map: "ClientConfigAdapter",
            dydx_perpetual_api_key: str,
            dydx_perpetual_api_secret: str,
            dydx_perpetual_passphrase: str,
            dydx_perpetual_ethereum_address: str,
            dydx_perpetual_stark_private_key: str,
            trading_pairs: Optional[List[str]] = None,
            trading_required: bool = True,
            domain: str = CONSTANTS.DEFAULT_DOMAIN,
    ):
        self._dydx_perpetual_api_key = dydx_perpetual_api_key
        self._dydx_perpetual_api_secret = dydx_perpetual_api_secret
        self._dydx_perpetual_passphrase = dydx_perpetual_passphrase
        self._dydx_perpetual_ethereum_address = dydx_perpetual_ethereum_address
        self._dydx_perpetual_stark_private_key = dydx_perpetual_stark_private_key
        self._trading_pairs = trading_pairs
        self._trading_required = trading_required
        self._domain = domain

        self._order_notional_amounts = {}
        self._current_place_order_requests = 0
        self._rate_limits_config = {}
        self._margin_fractions = {}
        self._position_id = None

        self._allocated_collateral = {}
        self._allocated_collateral_sum = Decimal("0")

        super().__init__(client_config_map=client_config_map)

    @property
    def name(self) -> str:
        return CONSTANTS.EXCHANGE_NAME

    @property
    def authenticator(self) -> DydxPerpetualAuth:
        return DydxPerpetualAuth(
            dydx_perpetual_api_key=self._dydx_perpetual_api_key,
            dydx_perpetual_api_secret=self._dydx_perpetual_api_secret,
            dydx_perpetual_passphrase=self._dydx_perpetual_passphrase,
            dydx_perpetual_ethereum_address=self._dydx_perpetual_ethereum_address,
            dydx_perpetual_stark_private_key=self._dydx_perpetual_stark_private_key,
        )

    @property
    def rate_limits_rules(self) -> List[RateLimit]:
        rate_limits = deepcopy(CONSTANTS.RATE_LIMITS)

        # Order cancelation
        for trading_pair in self._trading_pairs:
            rate_limits += [
                RateLimit(
                    limit_id=CONSTANTS.LIMIT_ID_ORDER_CANCEL + "_" + trading_pair,
                    limit=425,
                    time_interval=CONSTANTS.ONE_SECOND * 10,
                )
            ]

        # Order placement
        if "placeOrderRateLimiting" in self._rate_limits_config:
            for trading_pair in self._trading_pairs:
                rate_limits += [
                    RateLimit(
                        limit_id=CONSTANTS.LIMIT_ID_ORDER_PLACE + "_" + trading_pair,
                        limit=self._rate_limits_config["placeOrderRateLimiting"]["maxPoints"],
                        time_interval=self._rate_limits_config["placeOrderRateLimiting"]["windowSec"],
                    )
                ]

        for amount, amount_id in self._order_notional_amounts.items():
            weight = clamp(
                ceil(self._rate_limits_config["placeOrderRateLimiting"]["targetNotional"] / amount),
                self._rate_limits_config["placeOrderRateLimiting"]["minLimitConsumption"],
                self._rate_limits_config["placeOrderRateLimiting"]["maxOrderConsumption"],
            )

            for trading_pair in self._trading_pairs:
                rate_limits += [
                    RateLimit(
                        limit_id=CONSTANTS.LIMIT_ID_ORDER_PLACE + "_" + trading_pair + "_" + str(amount_id),
                        limit=self._rate_limits_config["placeOrderRateLimiting"]["maxPoints"],
                        time_interval=self._rate_limits_config["placeOrderRateLimiting"]["windowSec"],
                        linked_limits=[
                            LinkedLimitWeightPair(CONSTANTS.LIMIT_ID_ORDER_PLACE + "_" + trading_pair, weight)
                        ],
                    )
                ]

        return rate_limits

    @property
    def domain(self) -> str:
        return self._domain

    @property
    def client_order_id_max_length(self) -> int:
        return CONSTANTS.MAX_ID_LEN

    @property
    def client_order_id_prefix(self) -> str:
        return CONSTANTS.HBOT_BROKER_ID

    @property
    def trading_rules_request_path(self) -> str:
        return CONSTANTS.PATH_MARKETS

    @property
    def trading_pairs_request_path(self) -> str:
        return CONSTANTS.PATH_MARKETS

    @property
    def check_network_request_path(self) -> str:
        return CONSTANTS.PATH_TIME

    @property
    def trading_pairs(self) -> List[str]:
        return self._trading_pairs

    @property
    def is_cancel_request_in_exchange_synchronous(self) -> bool:
        return False

    @property
    def is_trading_required(self) -> bool:
        return self._trading_required

    @property
    def funding_fee_poll_interval(self) -> int:
        return 120

    def supported_order_types(self) -> List[OrderType]:
        return [OrderType.LIMIT, OrderType.LIMIT_MAKER, OrderType.MARKET]

    def _is_request_exception_related_to_time_synchronizer(self, request_exception: Exception) -> bool:
        return False

    def _is_request_result_an_error_related_to_time_synchronizer(self, request_result: Dict[str, Any]) -> bool:
        if "errors" in request_result and "msg" in request_result["errors"]:
            if "Timestamp must be within" in request_result["errors"]["msg"]:
                return True
        return False

    def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool:
        # TODO: implement this method correctly for the connector
        # The default implementation was added when the functionality to detect not found orders was introduced in the
        # ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update
        # when replacing the dummy implementation
        return False

    def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool:
        # TODO: implement this method correctly for the connector
        # The default implementation was added when the functionality to detect not found orders was introduced in the
        # ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the
        # dummy implementation
        return False

    async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder):
        try:
            body_params = {
                "market": await self.exchange_symbol_associated_to_pair(tracked_order.trading_pair),
                "side": "BUY" if tracked_order.trade_type == TradeType.BUY else "SELL",
                "id": tracked_order.exchange_order_id,
            }

            resp = await self._api_delete(
                path_url=CONSTANTS.PATH_ACTIVE_ORDERS,
                params=body_params,
                is_auth_required=True,
                limit_id=CONSTANTS.LIMIT_ID_ORDER_CANCEL + "_" + tracked_order.trading_pair,
            )
            if "cancelOrders" not in resp.keys():
                raise IOError(f"Error canceling order {order_id}.")
        except IOError as e:
            if any(error in str(e) for error in [CONSTANTS.ERR_MSG_NO_ORDER_FOR_MARKET]):
                await self._order_tracker.process_order_not_found(order_id)
            else:
                raise
        return True

    async def _place_order(
            self,
            order_id: str,
            trading_pair: str,
            amount: Decimal,
            trade_type: TradeType,
            order_type: OrderType,
            price: Decimal,
            position_action: PositionAction = PositionAction.NIL,
            **kwargs,
    ) -> Tuple[str, float]:
        if self._current_place_order_requests == 0:
            # No requests are under way, the dictionary can be cleaned
            self._order_notional_amounts = {}

        # Increment number of currently undergoing requests
        self._current_place_order_requests += 1

        if order_type.is_limit_type():
            time_in_force = CONSTANTS.TIF_GOOD_TIL_TIME
        else:
            time_in_force = CONSTANTS.TIF_IMMEDIATE_OR_CANCEL
            if trade_type.name.lower() == 'buy':
                # The price needs to be relatively high before the transaction, whether the test will be cancelled
                price = Decimal("1.5") * self.get_price_for_volume(
                    trading_pair,
                    True,
                    amount
                ).result_price
            else:
                price = Decimal("0.75") * self.get_price_for_volume(
                    trading_pair,
                    False,
                    amount
                ).result_price
            price = self.quantize_order_price(trading_pair, price)
        notional_amount = amount * price
        if notional_amount not in self._order_notional_amounts.keys():
            self._order_notional_amounts[notional_amount] = len(self._order_notional_amounts.keys())
            # Set updated rate limits
            self._throttler.set_rate_limits(self.rate_limits_rules)
        size = str(amount)
        price = str(price)
        side = "BUY" if trade_type == TradeType.BUY else "SELL"
        expiration = int(time.time()) + CONSTANTS.ORDER_EXPIRATION
        limit_fee = str(CONSTANTS.LIMIT_FEE)
        reduce_only = False

        post_only = order_type is OrderType.LIMIT_MAKER
        market = await self.exchange_symbol_associated_to_pair(trading_pair)

        signature = self._auth.get_order_signature(
            position_id=self._position_id,
            client_id=order_id,
            market=market,
            side=side,
            size=size,
            price=price,
            limit_fee=limit_fee,
            expiration_epoch_seconds=expiration,
        )

        data = {
            "clientId": order_id,
            "market": market,
            "side": side,
            "price": str(price),
            "size": str(amount),
            "type": CONSTANTS.ORDER_TYPE_MAP[order_type],
            "limitFee": limit_fee,
            "expiration": epoch_seconds_to_iso(expiration),
            "timeInForce": time_in_force,
            "reduceOnly": reduce_only,
            "postOnly": post_only,
            "signature": signature,
        }

        try:
            resp = await self._api_post(
                path_url=CONSTANTS.PATH_ORDERS,
                data=data,
                is_auth_required=True,
                limit_id=CONSTANTS.LIMIT_ID_ORDER_PLACE
                + "_"
                + trading_pair
                + "_"
                + str(self._order_notional_amounts[notional_amount]),
            )
        except Exception:
            self._current_place_order_requests -= 1
            raise

        # Decrement number of currently undergoing requests
        self._current_place_order_requests -= 1

        if "order" not in resp:
            raise IOError(f"Error submitting order {order_id}.")

        return str(resp["order"]["id"]), iso_to_epoch_seconds(resp["order"]["createdAt"])

    def _get_fee(
            self,
            base_currency: str,
            quote_currency: str,
            order_type: OrderType,
            order_side: TradeType,
            position_action: PositionAction,
            amount: Decimal,
            price: Decimal = s_decimal_NaN,
            is_maker: Optional[bool] = None,
    ) -> TradeFeeBase:
        is_maker = is_maker or False
        if CONSTANTS.FEES_KEY not in self._trading_fees.keys():
            fee = build_perpetual_trade_fee(
                self.name,
                is_maker,
                position_action=position_action,
                base_currency=base_currency,
                quote_currency=quote_currency,
                order_type=order_type,
                order_side=order_side,
                amount=amount,
                price=price,
            )
        else:
            fee_data = self._trading_fees[CONSTANTS.FEES_KEY]
            if is_maker:
                fee_value = Decimal(fee_data[CONSTANTS.FEE_MAKER_KEY])
            else:
                fee_value = Decimal(fee_data[CONSTANTS.FEE_TAKER_KEY])
            fee = AddedToCostTradeFee(percent=fee_value)
        return fee

    async def start_network(self):
        await super().start_network()
        await self._update_rate_limits()
        await self._update_position_id()

    async def _update_rate_limits(self):
        # Initialize rate limits for statically allocatedrequests
        self._throttler.set_rate_limits(self.rate_limits_rules)

        response: Dict[str, Dict[str, Any]] = await self._api_get(
            path_url=CONSTANTS.PATH_CONFIG,
            is_auth_required=False,
        )

        self._rate_limits_config["cancelOrderRateLimiting"] = response["cancelOrderRateLimiting"]
        self._rate_limits_config["placeOrderRateLimiting"] = response["placeOrderRateLimiting"]

        # Update rate limits with the obtained information
        self._throttler.set_rate_limits(self.rate_limits_rules)

    async def _update_position_id(self):
        if self._position_id is None:
            account = await self._get_account()
            self._position_id = account["positionId"]

    async def _update_trading_fees(self):
        response: Dict[str, Dict[str, Any]] = await self._api_get(
            path_url=CONSTANTS.PATH_CONFIG,
            is_auth_required=False,
        )

        self._trading_fees[CONSTANTS.FEES_KEY] = {
            CONSTANTS.FEE_MAKER_KEY: response["defaultMakerFee"],
            CONSTANTS.FEE_TAKER_KEY: response["defaultTakerFee"],
        }

    async def _user_stream_event_listener(self):
        async for event_message in self._iter_user_event_queue():
            try:
                event: Dict[str, Any] = event_message
                data: Dict[str, Any] = event["contents"]
                if "account" in data.keys() and len(data["account"]) > 0:
                    quote = "USD"
                    self._account_balances[quote] = Decimal(data["account"]["equity"])
                    # freeCollateral is sent only on start
                    self._account_available_balances[quote] = (
                        Decimal(data["account"]["quoteBalance"]) - self._allocated_collateral_sum
                    )
                    if "openPositions" in data["account"]:
                        await self._process_open_positions(data["account"]["openPositions"])

                if "accounts" in data.keys() and len(data["accounts"]) > 0:
                    for account in data["accounts"]:
                        quote = "USD"
                        # freeCollateral is sent only on start
                        self._account_available_balances[quote] = (
                            Decimal(account["quoteBalance"]) - self._allocated_collateral_sum
                        )

                if "orders" in data.keys() and len(data["orders"]) > 0:
                    for order in data["orders"]:
                        client_order_id: str = order["clientId"]
                        tracked_order = self._order_tracker.all_updatable_orders.get(client_order_id)
                        if tracked_order is not None:
                            trading_pair = await self.trading_pair_associated_to_exchange_symbol(order["market"])
                            state = CONSTANTS.ORDER_STATE[order["status"]]
                            new_order_update: OrderUpdate = OrderUpdate(
                                trading_pair=tracked_order.trading_pair,
                                update_timestamp=self.current_timestamp,
                                new_state=state,
                                client_order_id=tracked_order.client_order_id,
                                exchange_order_id=tracked_order.exchange_order_id,
                            )
                            self._order_tracker.process_order_update(new_order_update)
                        # Processing all orders of the account, not just the client's
                        if order["status"] in ["OPEN"]:
                            initial_margin_requirement = (
                                Decimal(order["price"])
                                * Decimal(order["size"])
                                * self._margin_fractions[trading_pair]["initial"]
                            )
                            initial_margin_requirement = abs(initial_margin_requirement)
                            self._allocated_collateral[order["id"]] = initial_margin_requirement
                            self._allocated_collateral_sum += initial_margin_requirement
                            self._account_available_balances[quote] -= initial_margin_requirement
                        if order["status"] in ["FILLED", "CANCELED"]:
                            if order["id"] in self._allocated_collateral:
                                # Only deduct orders that were previously in the OPEN state
                                # Some orders are filled instantly and reach only the PENDING state
                                self._allocated_collateral_sum -= self._allocated_collateral[order["id"]]
                                self._account_available_balances[quote] += self._allocated_collateral[order["id"]]
                                del self._allocated_collateral[order["id"]]

                if "fills" in data.keys() and len(data["fills"]) > 0:
                    trade_updates = self._process_ws_fills(data["fills"])
                    for trade_update in trade_updates:
                        self._order_tracker.process_trade_update(trade_update)

                if "positions" in data.keys() and len(data["positions"]) > 0:
                    # this is hit when a position is closed
                    positions = data["positions"]
                    for position in positions:
                        trading_pair = position["market"]
                        if trading_pair not in self._trading_pairs:
                            continue
                        position_side = PositionSide[position["side"]]
                        pos_key = self._perpetual_trading.position_key(trading_pair, position_side)
                        amount = Decimal(position.get("size"))

                        if amount != s_decimal_0:
                            position = Position(
                                trading_pair=trading_pair,
                                position_side=position_side,
                                unrealized_pnl=Decimal(position.get("unrealizedPnl", 0)),
                                entry_price=Decimal(position.get("entryPrice")),
                                amount=amount,
                                leverage=self.get_leverage(trading_pair),
                            )
                            self._perpetual_trading.set_position(pos_key, position)
                        else:
                            if self._perpetual_trading.get_position(pos_key):
                                self._perpetual_trading.remove_position(pos_key)

                if "fundingPayments" in data:
                    if event["type"] != CONSTANTS.WS_TYPE_SUBSCRIBED:  # Only subsequent funding payments
                        funding_payments = await self._process_funding_payments(data["fundingPayments"])
                        for trading_pair in funding_payments:
                            timestamp = funding_payments[trading_pair]["timestamp"]
                            funding_rate = funding_payments[trading_pair]["funding_rate"]
                            payment = funding_payments[trading_pair]["payment"]
                            self._emit_funding_payment_event(trading_pair, timestamp, funding_rate, payment, False)

            except asyncio.CancelledError:
                raise
            except Exception:
                self.logger().error("Unexpected error in user stream listener loop.", exc_info=True)

    async def _format_trading_rules(self, exchange_info_dict: Dict[str, Any]) -> List[TradingRule]:
        trading_rules = []
        markets_info = exchange_info_dict["markets"]
        for market_name in markets_info:
            market = markets_info[market_name]
            try:
                collateral_token = market["quoteAsset"]
                trading_rules += [
                    TradingRule(
                        trading_pair=market_name,
                        min_order_size=Decimal(market["minOrderSize"]),
                        min_price_increment=Decimal(market["tickSize"]),
                        min_base_amount_increment=Decimal(market["stepSize"]),
                        min_notional_size=Decimal(market["minOrderSize"]) * Decimal(market["tickSize"]),
                        supports_limit_orders=True,
                        supports_market_orders=True,
                        buy_order_collateral_token=collateral_token,
                        sell_order_collateral_token=collateral_token,
                    )
                ]
                self._margin_fractions[market_name] = {
                    "initial": Decimal(market["initialMarginFraction"]),
                    "maintenance": Decimal(market["maintenanceMarginFraction"]),
                }
            except Exception:
                self.logger().exception("Error updating trading rules")
        return trading_rules

    async def _update_balances(self):
        account = await self._get_account()
        quote = "USD"
        self._account_balances[quote] = Decimal(account["equity"])
        # freeCollateral is sent only on start
        self._account_available_balances[quote] = Decimal(account["quoteBalance"]) - self._allocated_collateral_sum

    def _process_ws_fills(self, fills_data: List) -> List[TradeUpdate]:
        trade_updates = []
        all_fillable_orders = self._order_tracker.all_fillable_orders
        for fill_data in fills_data:
            client_order_id: str = fill_data["orderClientId"]
            order = all_fillable_orders.get(client_order_id)
            trade_update = self._process_order_fills(fill_data=fill_data, order=order)
            if trade_update is not None:
                trade_updates.append(trade_update)
        return trade_updates

    async def _process_funding_payments(self, funding_payments: List):
        data = {}

        for trading_pair in self._trading_pairs:
            data[trading_pair] = {}
            data[trading_pair]["timestamp"] = 0
            data[trading_pair]["funding_rate"] = Decimal("-1")
            data[trading_pair]["payment"] = Decimal("-1")
            if trading_pair in self._last_funding_fee_payment_ts:
                data[trading_pair]["timestamp"] = self._last_funding_fee_payment_ts[trading_pair]

        prev_timestamps = {}

        for funding_payment in funding_payments:
            trading_pair = await self.trading_pair_associated_to_exchange_symbol(funding_payment["market"])

            if trading_pair not in prev_timestamps.keys():
                prev_timestamps[trading_pair] = None
            if (
                    prev_timestamps[trading_pair] is not None
                    and dateparse(funding_payment["effectiveAt"]).timestamp() <= prev_timestamps[trading_pair]
            ):
                continue
            timestamp = dateparse(funding_payment["effectiveAt"]).timestamp()
            funding_rate: Decimal = Decimal(funding_payment["rate"])
            payment: Decimal = Decimal(funding_payment["payment"])

            if trading_pair not in data:
                data[trading_pair] = {}

            data[trading_pair]["timestamp"] = timestamp
            data[trading_pair]["funding_rate"] = funding_rate
            data[trading_pair]["payment"] = payment

            prev_timestamps[trading_pair] = timestamp

        return data

    async def _process_open_positions(self, open_positions: Dict):
        for market, position in open_positions.items():
            trading_pair = await self.trading_pair_associated_to_exchange_symbol(symbol=market)
            position_side = PositionSide[position["side"]]
            pos_key = self._perpetual_trading.position_key(trading_pair, position_side)
            amount = Decimal(position.get("size"))
            if amount != s_decimal_0:
                entry_price = Decimal(position.get("entryPrice"))
                unrealized_pnl = Decimal(position.get("unrealizedPnl"))
                position = Position(
                    trading_pair=trading_pair,
                    position_side=position_side,
                    unrealized_pnl=unrealized_pnl,
                    entry_price=entry_price,
                    amount=amount,
                    leverage=self.get_leverage(trading_pair),
                )
                self._perpetual_trading.set_position(pos_key, position)
            else:
                self._perpetual_trading.remove_position(pos_key)

    async def _all_trade_updates_for_order(self, order: InFlightOrder) -> List[TradeUpdate]:
        trade_updates = []

        if order.exchange_order_id is not None:
            try:
                all_fills_response = await self._request_order_fills(order=order)
                fills_data = all_fills_response["fills"]
                trade_updates_tmp = self._process_rest_fills(fills_data)
                trade_updates += trade_updates_tmp

            except IOError as ex:
                if not self._is_request_exception_related_to_time_synchronizer(request_exception=ex):
                    raise
        return trade_updates

    def _process_rest_fills(self, fills_data: List) -> List[TradeUpdate]:
        trade_updates = []
        all_fillable_orders_by_exchange_order_id = {
            order.exchange_order_id: order for order in self._order_tracker.all_fillable_orders.values()
        }
        for fill_data in fills_data:
            exchange_order_id: str = fill_data["orderId"]
            order = all_fillable_orders_by_exchange_order_id.get(exchange_order_id)
            trade_update = self._process_order_fills(fill_data=fill_data, order=order)
            if trade_update is not None:
                trade_updates.append(trade_update)
        return trade_updates

    def _process_order_fills(self, fill_data: Dict, order: InFlightOrder) -> Optional[TradeUpdate]:
        trade_update = None
        if order is not None:
            fee_asset = order.quote_asset
            fee_amount = Decimal(fill_data["fee"])
            flat_fees = [] if fee_amount == Decimal("0") else [TokenAmount(amount=fee_amount, token=fee_asset)]
            position_side = fill_data["side"]
            position_action = (
                PositionAction.OPEN
                if (
                    order.trade_type is TradeType.BUY
                    and position_side == "BUY"
                    or order.trade_type is TradeType.SELL
                    and position_side == "SELL"
                )
                else PositionAction.CLOSE
            )

            fee = TradeFeeBase.new_perpetual_fee(
                fee_schema=self.trade_fee_schema(),
                position_action=position_action,
                percent_token=fee_asset,
                flat_fees=flat_fees,
            )

            trade_update = TradeUpdate(
                trade_id=fill_data["createdAt"],
                client_order_id=order.client_order_id,
                exchange_order_id=order.exchange_order_id,
                trading_pair=order.trading_pair,
                fill_timestamp=fill_data["createdAt"],
                fill_price=Decimal(fill_data["price"]),
                fill_base_amount=Decimal(fill_data["size"]),
                fill_quote_amount=Decimal(fill_data["price"]) * Decimal(fill_data["size"]),
                fee=fee,
            )
        return trade_update

    async def _request_order_fills(self, order: InFlightOrder) -> Dict[str, Any]:

        body_params = {
            "orderId": order.exchange_order_id,
            "limit": CONSTANTS.LAST_FILLS_MAX,
        }

        res = await self._api_get(
            path_url=CONSTANTS.PATH_FILLS,
            params=body_params,
            is_auth_required=True,
        )
        return res

    async def _request_order_status(self, tracked_order: InFlightOrder) -> OrderUpdate:
        if tracked_order.exchange_order_id is None:
            # Order placement failed previously
            order_update = OrderUpdate(
                client_order_id=tracked_order.client_order_id,
                trading_pair=tracked_order.trading_pair,
                update_timestamp=self.current_timestamp,
                new_state=OrderState.FAILED,
            )
        else:
            try:
                order_status_data = await self._request_order_status_data(tracked_order=tracked_order)
                order = order_status_data["order"]
                client_order_id = str(order["clientId"])

                order_update: OrderUpdate = OrderUpdate(
                    trading_pair=tracked_order.trading_pair,
                    update_timestamp=self.current_timestamp,
                    new_state=CONSTANTS.ORDER_STATE[order["status"]],
                    client_order_id=client_order_id,
                    exchange_order_id=order["id"],
                )
            except IOError as ex:
                if self._is_request_exception_related_to_time_synchronizer(request_exception=ex):
                    order_update = OrderUpdate(
                        client_order_id=tracked_order.client_order_id,
                        trading_pair=tracked_order.trading_pair,
                        update_timestamp=self.current_timestamp,
                        new_state=tracked_order.current_state,
                    )
                else:
                    raise
        return order_update

    async def _request_order_status_data(self, tracked_order: InFlightOrder) -> Dict:
        try:
            resp = await self._api_get(
                path_url=CONSTANTS.PATH_ORDERS + "/" + str(tracked_order.exchange_order_id),
                is_auth_required=True,
                limit_id=CONSTANTS.PATH_ORDERS,
            )
        except IOError as e:
            if any(error in str(e) for error in [CONSTANTS.ERR_MSG_NO_ORDER_FOUND]):
                await self._order_tracker.process_order_not_found(tracked_order.client_order_id)
            raise
        return resp

    def _create_web_assistants_factory(self) -> WebAssistantsFactory:
        return web_utils.build_api_factory(
            throttler=self._throttler,
            time_synchronizer=self._time_synchronizer,
            auth=self._auth,
        )

    def _create_order_book_data_source(self) -> DydxPerpetualAPIOrderBookDataSource:
        return DydxPerpetualAPIOrderBookDataSource(
            self.trading_pairs,
            connector=self,
            api_factory=self._web_assistants_factory,
            domain=self._domain,
        )

    def _create_user_stream_data_source(self) -> UserStreamTrackerDataSource:
        return DydxPerpetualUserStreamDataSource(dydx_auth=self._auth, api_factory=self._web_assistants_factory)

    def _initialize_trading_pair_symbols_from_exchange_info(self, exchange_info: Dict[str, Any]):
        markets = exchange_info["markets"]

        mapping = bidict()
        for key, val in markets.items():
            if val["status"] == "ONLINE":
                exchange_symbol = val["market"]
                base = val["baseAsset"]
                quote = val["quoteAsset"]
                trading_pair = combine_to_hb_trading_pair(base, quote)
                if trading_pair in mapping.inverse:
                    self._resolve_trading_pair_symbols_duplicate(mapping, exchange_symbol, base, quote)
                else:
                    mapping[exchange_symbol] = trading_pair

        self._set_trading_pair_symbol_map(mapping)

    def _resolve_trading_pair_symbols_duplicate(self, mapping: bidict, new_exchange_symbol: str, base: str, quote: str):
        """Resolves name conflicts provoked by futures contracts.
        If the expected BASEQUOTE combination matches one of the exchange symbols, it is the one taken, otherwise,
        the trading pair is removed from the map and an error is logged.
        """
        expected_exchange_symbol = f"{base}{quote}"
        trading_pair = combine_to_hb_trading_pair(base, quote)
        current_exchange_symbol = mapping.inverse[trading_pair]
        if current_exchange_symbol == expected_exchange_symbol:
            pass
        elif new_exchange_symbol == expected_exchange_symbol:
            mapping.pop(current_exchange_symbol)
            mapping[new_exchange_symbol] = trading_pair
        else:
            self.logger().error(
                f"Could not resolve the exchange symbols {new_exchange_symbol} and {current_exchange_symbol}"
            )
            mapping.pop(current_exchange_symbol)

    async def _get_last_traded_price(self, trading_pair: str) -> float:
        exchange_symbol = await self.exchange_symbol_associated_to_pair(trading_pair)
        params = {"market": exchange_symbol}

        response: Dict[str, Dict[str, Any]] = await self._api_get(
            path_url=CONSTANTS.PATH_MARKETS, params=params, is_auth_required=False
        )

        price = float(response["markets"][exchange_symbol]["indexPrice"])
        return price

    def supported_position_modes(self) -> List[PositionMode]:
        return [PositionMode.ONEWAY]

    def get_buy_collateral_token(self, trading_pair: str) -> str:
        trading_rule: TradingRule = self._trading_rules[trading_pair]
        return trading_rule.buy_order_collateral_token

    def get_sell_collateral_token(self, trading_pair: str) -> str:
        trading_rule: TradingRule = self._trading_rules[trading_pair]
        return trading_rule.sell_order_collateral_token

    async def _update_positions(self):
        account = await self._get_account()
        await self._process_open_positions(account["openPositions"])

    async def _trading_pair_position_mode_set(self, mode: PositionMode, trading_pair: str) -> Tuple[bool, str]:
        """
        :return: A tuple of boolean (true if success) and error message if the exchange returns one on failure.
        """
        if mode != PositionMode.ONEWAY:
            self.trigger_event(
                AccountEvent.PositionModeChangeFailed,
                PositionModeChangeEvent(
                    self.current_timestamp, trading_pair, mode, "dYdX only supports the ONEWAY position mode."
                ),
            )
            self.logger().debug(
                f"dYdX encountered a problem switching position mode to "
                f"{mode} for {trading_pair}"
                f" (dYdX only supports the ONEWAY position mode)"
            )
        else:
            self._position_mode = PositionMode.ONEWAY
            super().set_position_mode(PositionMode.ONEWAY)
            self.trigger_event(
                AccountEvent.PositionModeChangeSucceeded,
                PositionModeChangeEvent(self.current_timestamp, trading_pair, mode),
            )
            self.logger().debug(f"dYdX switching position mode to " f"{mode} for {trading_pair} succeeded.")

    async def _set_trading_pair_leverage(self, trading_pair: str, leverage: int) -> Tuple[bool, str]:
        success = True
        msg = ""

        response: Dict[str, Dict[str, Any]] = await self._api_get(
            path_url=CONSTANTS.PATH_MARKETS,
            is_auth_required=False,
        )

        if "markets" not in response:
            msg = "Failed to obtain markets information."
            success = False

        if success:
            markets_info = response["markets"]

            self._margin_fractions[trading_pair] = {
                "initial": Decimal(markets_info[trading_pair]["initialMarginFraction"]),
                "maintenance": Decimal(markets_info[trading_pair]["maintenanceMarginFraction"]),
            }

            max_leverage = int(Decimal("1") / self._margin_fractions[trading_pair]["initial"])
            if leverage > max_leverage:
                self._perpetual_trading.set_leverage(trading_pair=trading_pair, leverage=max_leverage)
                self.logger().warning(f"Leverage has been reduced to {max_leverage}")
            else:
                self._perpetual_trading.set_leverage(trading_pair=trading_pair, leverage=leverage)
        return success, msg

    async def _fetch_last_fee_payment(self, trading_pair: str) -> Tuple[int, Decimal, Decimal]:
        """
        Returns a tuple of the latest funding payment timestamp, funding rate, and payment amount.
        If no payment exists, return (0, -1, -1)
        """
        market = await self.exchange_symbol_associated_to_pair(trading_pair)

        body_params = {
            "market": market,
            "limit": CONSTANTS.LAST_FEE_PAYMENTS_MAX,
            "effectiveBeforeOrAt": generate_now_iso(),
        }

        response: Dict[str, Dict[str, Any]] = await self._api_get(
            path_url=CONSTANTS.PATH_FUNDING,
            params=body_params,
            is_auth_required=True,
        )

        funding_payments = await self._process_funding_payments(response["fundingPayments"])

        timestamp = funding_payments[trading_pair]["timestamp"]
        funding_rate = funding_payments[trading_pair]["funding_rate"]
        payment = funding_payments[trading_pair]["payment"]

        return timestamp, funding_rate, payment

    async def _get_account(self):
        account_id = self._auth.get_account_id()
        response: Dict[str, Dict[str, Any]] = await self._api_get(
            path_url=CONSTANTS.PATH_ACCOUNTS + "/" + str(account_id),
            limit_id=CONSTANTS.PATH_ACCOUNTS,
            is_auth_required=True,
        )

        if "account" not in response:
            raise IOError(f"Unable to get account info for account id {account_id}")

        return response["account"]


# coding = utf-8
# author = xy

import torch
from torch import nn
from torch.nn import functional as f
from modules.layers import embedding
from modules.layers import encoder
from modules.layers import pointer
from modules.layers import choose
import utils

class Model(nn.Module):
    """ Reinforced Mnemonic Reader for Machine Reading Comprehension 2018 """
    def __init__(self, param):
        super(Model, self).__init__()

        self.mode = param['mode']
        self.hidden_size = param['hidden_size']
        self.dropout_p = param['dropout_p']
        self.encoder_dropout_p = param['encoder_dropout_p']
        self.encoder_layer_num = param['encoder_layer_num']
        self.is_bn = param['is_bn']

        # embedding
        self.embedding = embedding.ExtendEmbedding(param['embedding'])

        # encoder: p
        input_size = self.embedding.embedding_dim
        self.encoder = encoder.Rnn(
            mode=self.mode,
            input_size=input_size,
            hidden_size=self.hidden_size,
            dropout_p=self.encoder_dropout_p,
            bidirectional=True,
            layer_num=self.encoder_layer_num,
            is_bn=self.is_bn
        )

        self.mean_q = pointer.AttentionPooling(self.hidden_size*2, self.hidden_size)
        self.mean_a = pointer.AttentionPooling(self.hidden_size*2, self.hidden_size)

        # align
        input_size = self.hidden_size * 2
        self.align_1 = Aligner(input_size, self.dropout_p, self.mode, self.is_bn, False)
        self.align_2 = Aligner(input_size, self.dropout_p, self.mode, self.is_bn, False)
        self.align_3 = Aligner(input_size, self.dropout_p, self.mode, self.is_bn, True)

        # p_rep, choosing
        self.wp1 = nn.Linear(self.hidden_size*2, self.hidden_size)
        self.wp2 = nn.Linear(self.hidden_size*2, self.hidden_size)
        self.vp = nn.Linear(self.hidden_size, 1)
        self.bi_linear = nn.Linear(self.hidden_size*2, self.hidden_size*2)

        self.dropout = nn.Dropout(self.dropout_p)

        self.reset_param()

    def reset_param(self):
        for module in self.modules():
            if isinstance(module, nn.Linear):
                nn.init.xavier_uniform_(module.weight, 0.1)

    def forward(self, batch):

        passage = batch[0: 3]
        query = batch[3: 6]
        zhengli = batch[6: 9]  # (batch_size, a1_len)
        fuli = batch[9: 12]
        wfqd = batch[12: 15]

        # mask
        passage_mask = utils.get_mask(passage[0])
        query_mask = utils.get_mask(query[0])
        zhengli_mask = utils.get_mask(zhengli[0])
        fuli_mask = utils.get_mask(fuli[0])
        wfqd_mask = utils.get_mask(wfqd[0])

        # embedding
        passage_vec = self.embedding(passage)
        query_vec = self.embedding(query)
        zhengli_vec = self.embedding(zhengli)
        fuli_vec = self.embedding(fuli)
        wfqd_vec = self.embedding(wfqd)

        # encoder: p, q
        passage_vec = self.encoder(passage_vec, passage_mask)  # (p_len, batch_size. hidden_size*2)
        passage_vec = self.dropout(passage_vec)
        query_vec = self.encoder(query_vec, query_mask)
        query_vec = self.dropout(query_vec)

        # encoder: zhengli,fuli,wfqd
        zhengli_vec = self.encoder(zhengli_vec, zhengli_mask)
        zhengli_vec = self.dropout(zhengli_vec)
        fuli_vec = self.encoder(fuli_vec, fuli_mask)
        fuli_vec = self.dropout(fuli_vec)
        wfqd_vec = self.encoder(wfqd_vec, wfqd_mask)
        wfqd_vec = self.dropout(wfqd_vec)

        # answer build
        zhengli_vec = self.mean_a(zhengli_vec, zhengli_mask)  # (batch_size, hidden_size*2)
        fuli_vec = self.mean_a(fuli_vec, fuli_mask)
        wfqd_vec = self.mean_a(wfqd_vec, wfqd_mask)

        answer = torch.stack([zhengli_vec, fuli_vec, wfqd_vec]).transpose(0, 1)  # (batch_size, 3, hidden_size)

        # attention: merge q into p
        R1, Z1, E1, B1 = self.align_1(passage_vec, passage_mask, query_vec, query_mask)
        R2, Z2, E2, B2 = self.align_2(R1, passage_mask, query_vec, query_mask, E1, B1)
        R3, _, _, _ = self.align_3(R2, passage_mask, query_vec, query_mask, E2, B2, Z1, Z2)
        p_prep = R3

        q_prep = self.mean_q(query_vec, query_mask).unsqueeze(0)  # (1, batch_size, hidden_size*2)
        sj = self.vp(torch.tanh(self.wp1(p_prep) + self.wp2(q_prep))).transpose(0, 1)  # (batch_size, p_len, 1)
        mask = passage_mask.eq(0).unsqueeze(2)
        sj.masked_fill_(mask, -float('inf'))
        sj = f.softmax(sj, dim=1).transpose(1, 2)  # (batch_size, 1, p_len)
        p_prep = torch.bmm(sj, p_prep.transpose(0, 1)).squeeze(1)  # (batch_size, hidden_size*2)

        # choosing
        p_prep = self.bi_linear(p_prep)  # (batch_size, hidden_size)
        outputs = torch.bmm(answer, p_prep.unsqueeze(2)).squeeze(2)  # (batch_size, 3)

        return outputs

class Aligner(nn.Module):
    def __init__(self, input_size, dropout_p, mode, is_bn, use_rnn):
        super(Aligner, self).__init__()

        self.inter_align = InterAlign(input_size, dropout_p)
        self.self_align = SelfAlign(input_size, dropout_p)
        self.aggregation = EviCollection(mode, input_size, dropout_p, is_bn)

        self.use_rnn = use_rnn
        if use_rnn:
            self.rnn = encoder.Rnn(
                mode=mode,
                input_size=input_size*3,
                hidden_size=input_size//2,
                dropout_p=dropout_p,
                bidirectional=True,
                layer_num=1,
                is_bn=is_bn
            )

    def forward(self, U, U_mask, V, V_mask, E=None, B=None, Z1=None, Z2=None):
        """
        :param U: (c_len, batch_size, input_size)
        :param V: (q_len, batch_size, input_size)
        :param E: (batch_size, c_len, q_len)
        :param B:
        :param Z1: (c_len, batch_size, input_size)
        :param Z2:
        :return:R,Z,E,B
        """
        h, E = self.inter_align(U, U_mask, V, V_mask, E, B)
        Z, B = self.self_align(h, U_mask, B)
        if self.use_rnn:
            z = torch.cat([Z1, Z2, Z], dim=2)
            R = self.rnn(z, U_mask)
        else:
            R = self.aggregation(Z, U_mask)

        return R, Z, E, B

class InterAlign(nn.Module):
    def __init__(self, input_size, dropout_p):
        super(InterAlign, self).__init__()

        self.Wu = nn.Linear(input_size, input_size)
        self.Wv = nn.Linear(input_size, input_size)
        self.gamma = nn.Parameter(torch.tensor(3.0))
        self.sfu = SFU(input_size, dropout_p)
        self.dropout = nn.Dropout(dropout_p)

    def forward(self, content_vec, content_mask, question_vec, question_mask, E_t=None, B_t=None):
        """
        :param content_vec: (c_len, batch_size, input_size)
        :param content_mask: (batch_size, c_len)
        :param question_vec: (q_len, batch_size, input_size)
        :param question_mask: (batch_size, q_len)
        :param E_t: (batch_size, c_len, q_len)  or None
        :param B_t: (batch_size, c_len, c_len) or None
        :return: h(c_len, batch_size, input_size), E_t(batch_size, c_len, q_len)
        """

        content_vec = self.dropout(content_vec)
        question_vec = self.dropout(question_vec)

        # E_t
        content_vec_tmp = f.relu(self.Wu(content_vec)).transpose(0, 1)  # (batch_size, c_len, input_size)
        question_vec_tmp = f.relu(self.Wv(question_vec)).transpose(0, 1)  # (batch_size, q_len, input_size)
        E_0 = torch.bmm(content_vec_tmp, question_vec_tmp.transpose(1, 2))  # (batch_size, c_len, q_len)

        if E_t is not None:
            E_t_mask = content_mask.eq(0).unsqueeze(2).expand(E_t.size())
            E_t = E_t.masked_fill(E_t_mask, -float('inf'))
            E_t = f.softmax(E_t, dim=1)  # (batch_size, c_len, q_len)

            B_t_mask = content_mask.eq(0).unsqueeze(1).expand(B_t.size())
            B_t = B_t.masked_fill(B_t_mask, -float('inf'))
            B_t = f.softmax(B_t, dim=2)  # (batch_size, c_len, c_len)

            E_1 = torch.bmm(B_t, E_t)
            E_t = E_0 + self.gamma * E_1  # (batch_size, c_len, q_len)
        else:
            E_t = E_0

        # V_bar
        mask = question_mask.eq(0).unsqueeze(1).expand(E_t.size())
        E_tt = E_t.masked_fill(mask, -float('inf'))
        E_tt = f.softmax(E_tt, dim=2)  # (batch_size, c_len, q_len)
        question_vec_tmp = torch.bmm(E_tt, question_vec.transpose(0, 1))  # (batch_size, c_len, input_size)
        question_vec_tmp = question_vec_tmp.transpose(0, 1)  # (c_len, batch_size, input_size)

        # fusion
        h = self.sfu(content_vec, question_vec_tmp)  # (c_len, batch_size, input_size)

        return h, E_t

class SelfAlign(nn.Module):
    def __init__(self, input_size, dropout_p):
        super(SelfAlign, self).__init__()

        self.W1 = nn.Linear(input_size, input_size)
        self.W2 = nn.Linear(input_size, input_size)
        self.gamma = nn.Parameter(torch.tensor(3.0))
        self.sfu = SFU(input_size, dropout_p)
        self.dropout = nn.Dropout(dropout_p)

    def forward(self, h, h_mask, B_t=None):
        """
        :param h: (c_len, batch_size, input_size)
        :param h_mask: (batch_size, c_len)
        :param B_t: (batch_size, c_len_1, c_len_2) or None
        :return: h(c_len, batch_size, input_size), B_t(batch_size, c_len, c_len)
        """

        h = self.dropout(h)  # (c_len_2, batch_size, input_size)

        # B_t
        h_tmp_1 = f.relu(self.W1(h)).transpose(0, 1)  # (batch_size, c_len_1, input_size)
        h_tmp_2 = f.relu(self.W2(h)).transpose(0, 1)  # (batch_size, c_len_2, input_size)
        B_0 = torch.bmm(h_tmp_1, h_tmp_2.transpose(1, 2))  # (batch_size, c_len_1, c_len_2)

        if B_t is not None:
            mask_1 = h_mask.eq(0).unsqueeze(2).expand(B_t.size())
            B_t_1 = B_t.masked_fill(mask_1, -float('inf'))
            B_t_1 = f.softmax(B_t_1, dim=1)  # (batch_size, c_len_1, c_len_2)

            mask_2 = h_mask.eq(0).unsqueeze(1).expand(B_t.size())
            B_t_2 = B_t.masked_fill(mask_2, -float('inf'))
            B_t_2 = f.softmax(B_t_2, dim=2)  # (batch_size, c_len_1, c_len_1)

            B_1 = torch.bmm(B_t_2, B_t_1)  # (batch_size, c_len_1, c_len_2)
            B_t = B_0 + self.gamma * B_1

        else:
            B_t = B_0

        # make dialog
        mask = torch.eye(h.size(0), dtype=torch.uint8).cuda()
        mask = mask.unsqueeze(0)
        B_t.masked_fill_(mask, 0.0)

        # h_bar
        mask = h_mask.eq(0).unsqueeze(1).expand(B_t.size())
        B_tt = B_t.masked_fill(mask, -float('inf'))
        B_tt = f.softmax(B_tt, dim=2)
        h_tmp = torch.bmm(B_tt, h.transpose(0, 1))  # (batch_size, c_len_1, input_size)
        h_tmp = h_tmp.transpose(0, 1)  # (c_len_1, batch_size, input_size)

        # fusion
        h = self.sfu(h, h_tmp)  # (c_len_1, batch_size, input_size)

        return h, B_t

class EviCollection(nn.Module):
    def __init__(self, mode, input_size, dropout_p, is_bn):
        super(EviCollection, self).__init__()

        self.rnn = encoder.Rnn(
            mode=mode,
            input_size=input_size,
            hidden_size=input_size//2,
            dropout_p=dropout_p,
            bidirectional=True,
            layer_num=1,
            is_bn=is_bn
        )

    def forward(self, z, z_mask):
        """
        :param z: (c_len, batch_size, input_size)
        :return: (c_len, batch_size, input_size)
        """

        o = self.rnn(z, z_mask)
        return o

class SFU(nn.Module):
    def __init__(self, input_size, dropout_p):
        super(SFU, self).__init__()
        self.Wr = nn.Linear(input_size*4, input_size, bias=False)
        self.Wg = nn.Linear(input_size*4, input_size, bias=False)

        self.dropout = nn.Dropout(dropout_p)

    def forward(self, inputs, fusion):
        """
        :param inputs: (.., input_size)
        :param fusion: (.., fusion_size)
        :return: (.., input_size)
        """
        m = torch.cat([inputs, fusion, inputs*fusion, inputs-fusion], dim=-1)
        m = self.dropout(m)
        x = f.relu(self.Wr(m))
        g = torch.sigmoid(self.Wg(m))
        o = x*g + (1-g)*inputs

        return o


from xml_parser import parse_frame_dump, list2array
from foundation import load_pcd_data, points2pcd, draw, fps
from math_util import rotate_mat, rotation_matrix_from_vectors
import pickle
from xml.dom.minidom import Document
import copy
import time
from shutil import copyfile
import open3d as o3d
import numpy as np
import pandas as pd
class WeldScene:
    '''
    Component point cloud processing, mainly for slicing

    Attributes:
        path_pc: path to labeled pc

    '''

    def __init__(self, pc_path):
        self.pc = o3d.geometry.PointCloud()
        xyzl = load_pcd_data(pc_path)
        # print (xyzl.shape)
        self.xyz = xyzl[:, 0:3]
        self.l = xyzl[:, 3]
        self.pc.points = o3d.utility.Vector3dVector(self.xyz)

    def crop(self, weld_info, weld_info2,crop_size=400, num_points=2048, vis=True):
        '''Cut around welding spot

        Args:
            weld_info (np.ndarray): welding info, including torch type, weld position, surface normals, torch pose
            crop_size (int): side length of cutting bbox in mm
            num_points (int): the default point cloud contains a minimum of 2048 points, if not enough then copy and fill
            vis (Boolean): True for visualization of the slice while slicing
        Returns:
            xyzl_crop (np.ndarray): cropped pc with shape num_points*4, cols are x,y,z,label
            cropped_pc (o3d.geometry.PointCloud): cropped pc for visualization
            weld_info (np.ndarray): update the rotated component pose for torch (if there is)
        '''
        pc = copy.copy(self.pc)

        # tow surface normals at the welding spot
        norm1 = np.around(weld_info[4:7], decimals=6)
        norm2 = np.around(weld_info[7:10], decimals=6)
        extent = crop_size - 10
        crop_extent = np.array([extent, extent, extent])
        weld_spot = weld_info[1:4]
        # move the coordinate center to the welding spot
        pc.translate(-weld_spot)
        # rotation at this welding spot
        rot = weld_info[10:13] * np.pi / 180
        rotation = rotate_mat(axis=[1, 0, 0], radian=rot[0])
        # torch pose
        pose = np.zeros((3, 3))
        pose[0:3, 0] = weld_info[14:17]
        pose[0:3, 1] = weld_info[17:20]
        pose[0:3, 2] = weld_info[20:23]
        # cauculate the new pose after rotation
        pose_new = np.matmul(rotation, pose)
        tf = np.zeros((4, 4))
        tf[3, 3] = 1.0
        tf[0:3, 0:3] = rotation
        pc.transform(tf)
        # new normals
        norm1_r = np.matmul(rotation, norm1.T)
        norm2_r = np.matmul(rotation, norm2.T)

        weld_info[4:7] = norm1_r
        weld_info[7:10] = norm2_r
        weld_info[14:17] = pose_new[0:3, 0]
        weld_info[17:20] = pose_new[0:3, 1]
        weld_info[20:23] = pose_new[0:3, 2]

        coor1 = o3d.geometry.TriangleMesh.create_coordinate_frame(size=200, origin=[0, 0, 0])
        norm_ori = np.array([0, 0, 1])
        # bounding box of cutting area
        R = rotation_matrix_from_vectors(norm_ori, norm_ori)
        bbox = o3d.geometry.OrientedBoundingBox(center=(extent / 2 - 60) * norm1_r + (extent / 2 - 60) * norm2_r, R=R,
                                                extent=crop_extent)
        cropped_pc = pc.crop(bbox)
        idx_crop = bbox.get_point_indices_within_bounding_box(pc.points)
        xyz_crop = self.xyz[idx_crop]
        xyz_crop -= weld_spot
        xyz_crop_new = np.matmul(rotation, xyz_crop.T).T
        l_crop = self.l[idx_crop]
        xyzl_crop = np.c_[xyz_crop_new, l_crop]
        xyzl_crop = np.unique(xyzl_crop, axis=0)
        while xyzl_crop.shape[0] < num_points:
            xyzl_crop = np.vstack((xyzl_crop, xyzl_crop))
        xyzl_crop = fps(xyzl_crop, num_points)
        if vis:
            o3d.visualization.draw_geometries([cropped_pc, coor1, bbox])
        return xyzl_crop, cropped_pc, weld_info

pc_path='Reisch.pcd'
ws = WeldScene(pc_path)
xml_path='Reisch.xml'
frames = list2array(parse_frame_dump(xml_path))
weld_info=[]
for i in range(0,200):
    tmp=frames[frames[:,-2]==str(i)]
    if len(tmp)!=0:
        weld_info.append(tmp)
weld_info=np.array(weld_info)
weld_info=weld_info[:,3:].astype(float)
weld_info1=weld_info[0]
weld_info2=weld_info[2]
cxyzl, cpc, new_weld_info = ws.crop(weld_info=weld_info1,weld_info2=weld_info2, crop_size=crop_size, num_points=num_points)
# for i in range(weld_info.shape[0]):
#     weld_info=weld_info[0]
# print(weld_info)
# print(weld_info)
# print(weld_info.shape)
# print(len(weld_info))

# point_cloud = o3d.io.read_point_cloud("Reisch.pcd")
# , [-1401.280148093301, 83.93862361422825, 2182.061376385556]
# plane_point = np.array([[-796.2801480933575, 83.9386236142102, 2182.061376385546]])
# plane_normal = np.array([-1.010719286043127e-13,-1.000000000000427,-1.804112415015879e-14])
# sliced_cloud = point_cloud.crop(plane_point, plane_normal, negative=False)
# o3d.visualization.draw_geometries([point_cloud])


from chemistry_lang import evaluate
from chemistry_lang.objs import CHQuantity, CHNumber
from .utils import initialize, reset, assert_stdout

initialize()

def test_redo():
    reset()
    src = """
redo i of 1...5
    redo j of 1...5
        print(i * j)
    """
    evaluate(src)

    expected = ""
    for i in range(1, 5):
        for j in range(1, 5):
            expected += str(CHQuantity(None, CHNumber(i), None) * CHQuantity(None, CHNumber(j), None)) + "\n"
    assert_stdout(expected)

def test_during():
    reset()
    src = """
i = 10
during i >= 0
    print(i)
    i -= 1
print('Done!')
    """
    evaluate(src)

    expected = ""
    for i in range(10, -1, -1):
        expected += str(CHQuantity(None, CHNumber(i), None)) + "\n"
    expected += "Done!\n"
    assert_stdout(expected)


from typing import List

class Solution:
    def removeDuplicates(self, nums: List[int]) -> int:
        if not nums:
            return 0

        k = 1  # Initialize the count of unique elements

        for i in range(1, len(nums)):
            if nums[i] != nums[k - 1]:
                nums[k] = nums[i]
                k += 1

        return k


import logging
import os

import torch

class Checkpointer(object):
    def __init__(
            self,
            model,
            optimizer=None,
            scheduler=None,
            save_dir="",
            logger=None,
    ):
        self.model = model
        self.optimizer = optimizer
        self.scheduler = scheduler
        self.save_dir = save_dir
        if logger is None:
            logger = logging.getLogger(__name__)
        self.logger = logger

    def save(self, name, **kwargs):
        if not self.save_dir:
            return

        data = {}
        data["model"] = self.model.state_dict()
        if self.optimizer is not None:
            data["optimizer"] = self.optimizer.state_dict()
        if self.scheduler is not None:
            data["scheduler"] = self.scheduler.state_dict()
        data.update(kwargs)

        save_file = os.path.join(self.save_dir, "{}.pth".format(name))
        self.logger.info("Saving checkpoint to {}".format(save_file))
        torch.save(data, save_file)
        self.tag_last_checkpoint(save_file)

    def load(self, f=None, resume=True):
        if resume and self.has_checkpoint():
            # override argument with existing checkpoint
            f = self.get_checkpoint_file()
        if not f:
            # no checkpoint could be found
            self.logger.info("No checkpoint found. Initializing model from scratch")
            return {}
        self.logger.info("Loading checkpoint from {}".format(f))
        checkpoint = self._load_file(f)
        self.model.load_state_dict(checkpoint.pop("model"), False)
        if "optimizer" in checkpoint and self.optimizer:
            self.logger.info("Loading optimizer from {}".format(f))
            self.optimizer.load_state_dict(checkpoint.pop("optimizer"))
        if "scheduler" in checkpoint and self.scheduler:
            self.logger.info("Loading scheduler from {}".format(f))
            self.scheduler.load_state_dict(checkpoint.pop("scheduler"))

        # return any further checkpoint data
        return checkpoint

    def has_checkpoint(self):
        save_file = os.path.join(self.save_dir, "last_checkpoint")
        return os.path.exists(save_file)

    def get_checkpoint_file(self):
        save_file = os.path.join(self.save_dir, "last_checkpoint")
        try:
            with open(save_file, "r") as f:
                last_saved = f.read()
        except IOError:
            # if file doesn't exist, maybe because it has just been
            # deleted by a separate process
            last_saved = ""
        return last_saved

    def tag_last_checkpoint(self, last_filename):
        save_file = os.path.join(self.save_dir, "last_checkpoint")
        with open(save_file, "w") as f:
            f.write(last_filename)

    def _load_file(self, f):
        return torch.load(f, map_location=torch.device("cpu"))


from cProfile import label
from cgitb import reset
from datetime import datetime
from operator import concat
from pickle import DICT
# from performance_anaysis.portfolio import portfolio_from_degiro_transactions
from numpy.core.fromnumeric import product
import yfinance as yf
from pprint import pprint
from config import db
from pandas import DataFrame
import pandas as pd
import yahoo_fin.stock_info as si
import numpy as np
import matplotlib.pyplot as plt
from bson.objectid import ObjectId
from pandasgui import show

def add_assets(portfolio):
    allocations=[]
    for allc in portfolio['allocation']:
        new_allc= allc
        itm = db.stocks.find_one({"symbol":allc['Symbol']})
        try:
            allc['asset']= itm
        except:
            allc['asset']= None
        allocations.append(allc)
    db.portfolios.update_one({'name':"us dividends"},{'$set':{"allocation": allocations}})

def add_transaction(trs):
    db.portfolios.update_one({'name':"current"},{'$push':{"transactions": trs}})

def creat_allocation(pf_name):
    pf= db.portfolios.find_one({'name':pf_name})
    transactions= pf['transactions']
    df=DataFrame.from_dict(transactions)
    alloc= DataFrame()
    alloc['qty']= df.groupby("symbol") ['qty'].sum()
    alloc['bep']= df.groupby("symbol") ['price'].mean()
    # alloc['bep_eur']= df.groupby("symbol") ['value'].mean()
    alloc= alloc.reset_index()
    symbols=alloc['symbol'].tolist()
    stocks= db.stocks.find({'symbol':{'$in': symbols}})
    stocks_dict = dict((s["symbol"], s) for s in stocks)

    alloc['asset'] = alloc['symbol'].apply(lambda x: ObjectId(stocks_dict[x]['_id']))
    balance= DataFrame.from_dict(pf['cash_flow'])['amount'].sum()
    alloc['last']=alloc['symbol'].apply(lambda x: stocks_dict[x]['last'])
    alloc['total_value']= alloc['qty']*alloc['symbol'].apply(lambda x: stocks_dict[x]['last'])
    if alloc['total_value'].sum()>balance:
        balance=alloc['total_value'].sum()

    alloc['weight']= alloc['total_value']/balance
    alloc= alloc.loc[alloc['weight']>0]
    allocation= list(alloc.to_dict(orient='index').values())
    pprint(allocation)
    db.portfolios.update_one({'name':pf_name},{'$set': {'allocation':allocation}})

def update_allocation(pf_name):
    pf= db.portfolios.find_one({'name':pf_name})
    alloc= pf['allocation']
    alloc=DataFrame.from_dict(alloc)

    symbols=alloc['symbol'].tolist()
    stocks= db.stocks.find({'symbol':{'$in': symbols}})
    stocks_dict = dict((s["symbol"], s) for s in stocks)

    alloc['asset'] = alloc['symbol'].apply(lambda x: ObjectId(stocks_dict[x]['_id']))
    balance= DataFrame.from_dict(pf['cash_flow'])['amount'].sum()
    alloc['last']=alloc['symbol'].apply(lambda x: stocks_dict[x]['last'])
    # alloc['total_value']= alloc['weight']*alloc['symbol'].apply(lambda x: stocks_dict[x]['last'])
    # if alloc['total_value'].sum()>balance:
    #     balance=alloc['total_value'].sum()

    # alloc['weight']= alloc['total_value']/balance
    # alloc= alloc.loc[alloc['weight']>0]
    allocation= list(alloc.to_dict(orient='index').values())
    pprint(allocation)
    db.portfolios.update_one({'name':pf_name},{'$set': {'allocation':allocation}})

def dump_ex_transactions():
    transaction_ex= {
                'symbol':'MSFT',
                'action': 'buy',
                'qty':1,
                'date': datetime.strptime("15-09-2021",'%d-%m-%Y'),
                'price': 289,
                'value': 240,
                }
    transaction_ex_2= {
                'symbol':'STM.PA',
                'action': 'buy',
                'qty':1,
                'date': datetime.strptime("15-09-2021",'%d-%m-%Y'),
                'price': 40,
                'value': 40,
                }
    add_transaction(transaction_ex_2)
    add_transaction(transaction_ex)

def add_cash_flow(trs):
    db.portfolios.update_one({'name':"current"},{'$push':{"cash_flow": trs}})
def dump_ex_cash():
    amount_1= {
                'action': 'deposite',
                'date': datetime.strptime("15-09-2021",'%d-%m-%Y'),
                'amount': 800,
                }
    amount_2= {
                'action': 'withdrow',
                'date': datetime.strptime("15-09-2021",'%d-%m-%Y'),
                'amount': -40,
                }
    add_cash_flow(amount_1)
    add_cash_flow(amount_2)

def calculate_performance(pf_name):
    print("calculate perfs for: "+pf_name)
    transactions=db.portfolios.find_one({'name': pf_name})['transactions']
    transactions= DataFrame(transactions)
    stocks= list(set(transactions['symbol']))
    transactions=transactions.sort_values(by='date')
    date_min= transactions['date'].iloc[0]
    date_max= datetime.now()

    dates= pd.date_range(date_min, date_max).tolist()
    portfolio = pd.DataFrame(0, index=np.arange(len(dates)),columns= stocks)
    stocks_weight= pd.DataFrame(0, index=np.arange(len(dates)),columns= stocks)
    portfolio_returns= pd.DataFrame(0, index=np.arange(len(dates)),columns= stocks)
    portfolio['date'] = dates
    stocks_weight['date']= dates
    initial_cash=460
    for idx,d in transactions.iterrows():
        stocks_weight.loc[portfolio['date']>=d['date'], d['symbol']] = d['price']/initial_cash
        portfolio.loc[portfolio['date']>=d['date'], d['symbol']] =portfolio.loc[portfolio['date']==d['date']].iloc[0][d['symbol']] + d['qty']

    result= pd.DataFrame(0, index=np.arange(len(dates)),columns= stocks)
    result['date']=portfolio['date']
    portfolio_returns['date']=portfolio['date']
    result=result.set_index('date')
    stocks_weight= stocks_weight.set_index('date')
    portfolio=portfolio.set_index('date')
    portfolio_returns=portfolio_returns.set_index('date')

    for s in stocks:
        stocks_returns= yf.Ticker(s).history(start=date_min,end=date_max,period="1d")['Close']
        stocks_returns= stocks_returns.pct_change()
        portfolio_returns[s]=stocks_returns
        float_portfolio=portfolio[s].astype('float64')
        result[s]= stocks_returns*float_portfolio * stocks_weight[s]

    stocks_weight= stocks_weight*100
    perf= DataFrame()
    perf['sum']= result.sum(axis=1)
    perf['cum_All'] = (1+perf['sum']).cumprod()
    perf['cum_1Y'] = (1+perf['sum'].tail(365)).cumprod()
    perf['cum_6M'] = (1+perf['sum'].tail(182)).cumprod()
    perf['cum_1M'] = (1+perf['sum'].tail(30)).cumprod()
    perf= perf.reset_index()
    res= perf.to_dict('list')
    db.portfolios.update_one({'name':pf_name},{'$set':{"perfs": res,"last_perfs_update": datetime.now() }})
    return perf

def calculate_performance_fixed(id):
    print("calculate perfs for: "+id)
    pft=db.portfolios.find_one({'_id': ObjectId(id)})
    transactions=pft['transactions']
    transactions= DataFrame(transactions)
    stocks= list(set(transactions['symbol']))
    transactions=transactions.sort_values(by='date')
    date_min= transactions['date'].iloc[0]
    print(date_min)

    df_stocks= get_stocks(stocks,date_min).reset_index().sort_values(by='Date')
    # df_stocks.dropna(inplace=True)
    portfolio = pd.DataFrame(0, index=np.arange(len(df_stocks['Date'])),columns= stocks+['cash','apport','invested'])
    portfolio['Date'] = df_stocks['Date']

    movements=list(pft['cash_flow'])
    for m in movements:
         portfolio.loc[portfolio['Date']>=m['date'], 'apport'] += m['amount']

    for i,transaction in transactions.iterrows():
        portfolio.loc[portfolio['Date']>=transaction['date'], transaction['symbol']] += transaction['qty']
        portfolio.loc[portfolio['Date']>=transaction['date'], 'invested'] += float(transaction['price'])

    portfolio['cash'] = portfolio['apport']- portfolio['invested']
    portfolio=portfolio.sort_values(by='Date')

    df_stocks= df_stocks.set_index('Date')
    portfolio=portfolio.set_index('Date')

    result= df_stocks[stocks]*portfolio[stocks]
    # plot_graph_multiple(result[['IHPCF']])
    result['value']= result.sum(axis=1)
    result['pnl']= result['value'] - portfolio['invested']
    result['cash']= portfolio['cash']
    result['apport']= portfolio['apport']
    result['invested']= portfolio['invested']
    result['total']= result['value']+ portfolio['cash']
    result['daily_returns']= (result['pnl']+1).pct_change()
    result['all']= result['invested']+ np.where(result['cash']>0, result['cash'], 0)
    result['performance']= result['pnl']/result['all']*100
    result.dropna(inplace=True)
    perf= result[['performance','total','pnl']].reset_index().rename(columns={'Date':'date'})
    show(result)
    plot_graph(result['total'])
    res= perf.to_dict('list')
    db.portfolios.update_one({'_id':ObjectId(id)},{'$set':{"perfs": res,"last_perfs_update": datetime.now() }})
    return perf

def get_stocks(stocks,date_min):
    start= datetime.now()
    stockArray= []
    for s in stocks:
        stock=db.histories.find_one({'symbol': s})
        if stock is None:
            stock= {'history':{}}

        if not('last_update' in stock) or stock['last_update'].date()<datetime.today().date():
            print(s,date_min)
            stock['history']= yf.Ticker(s).history(start=date_min,period="1d").reset_index()[['Date','Close']].to_dict('records')
            stock['last_update']=datetime.today()
            db.histories.replace_one({'symbol': s}, stock)

        df= pd.DataFrame(stock['history'],columns=['Date','Close']).set_index('Date').rename(columns={"Close":s})
        stockArray.append(df)
    stockDf= pd.concat(stockArray,axis=1, join='outer')
    end= datetime.now() -start
    print('get_stocks executed in: '+str(end))
    return stockDf

def get_dividends(id):
    print("calculate perfs for: "+id)
    start= datetime.now()
    pft=db.portfolios.find_one({'_id': ObjectId(id)})
    transactions=pft['transactions']
    transactions= DataFrame(transactions)
    stocks= list(set(transactions['symbol']))
    transactions=transactions.sort_values(by='date')
    date_min= transactions['date'].iloc[0]
    if(type(date_min)==str):
        date_min= datetime.strptime(date_min, '%Y-%m-%d')
    dividensArray= []

    for s in stocks:
        stock=db.dividends.find_one({'symbol': s})
        if stock is None:
            stock= {'dividends':{}}
        if  not('dividends' in stock) or not('last_update' in stock) or stock['last_update'].date()<datetime.today().date():
            stock['dividends']= yf.Ticker(s).get_dividends().to_frame().reset_index().to_dict('records')
            stock['last_update']=datetime.today()
            db.dividends.replace_one({'symbol': s}, stock,upsert=True)
        df= pd.DataFrame(stock['dividends'],columns=['Date','Dividends']).set_index('Date').rename(columns={"Dividends":s})
        dividensArray.append(df)

    dividends_df= pd.concat(dividensArray,axis=1, join='outer')
    print(date_min)
    dividends_df=dividends_df.loc[dividends_df.index>=date_min]

    portfolio = pd.DataFrame(0, index=np.arange(len(dividends_df.index)),columns= stocks)
    portfolio['Date']= dividends_df.index
    for i,transaction in transactions.iterrows():
        portfolio.loc[portfolio['Date']>=transaction['date'], transaction['symbol']] += transaction['qty']

    portfolio=portfolio.set_index('Date')
    res= dividends_df*portfolio
    res= res.sort_index()
    res['values']= res.sum(axis=1)
    res.index = pd.to_datetime(res.index)
    monthy= res['values'].resample('MS').sum().reset_index().to_dict('list')
    yearly= res['values'].resample('ys').sum().reset_index().to_dict('list')
    end= datetime.now() -start
    print('get_dividends executed in: '+str(end))
    dividends= {'monthy':monthy,'yearly':yearly}
    db.portfolios.update_one({'_id':ObjectId(id)},{'$set':{"dividends": dividends}})

def test():
    # res=get_stocks(['MSFT','AAPL','ACA.PA'],"2021-01-01")
    # print(res)
    res=calculate_performance_fixed('6315177eababbe0d50f66376')
    # get_dividends('62b38bb44bccfe2988898a2b')

def plot_graph(df):
    """
    This function plots true close price along with predicted close price
    with blue and red colors respectively
    """
    fig = plt.figure(figsize=(10,5))
    plt.plot(df, c='b')
    plt.xlabel("Days")
    plt.ylabel("Price")
    plt.legend(["Actual Price", "Predicted Price"])
    # Saving the plot as an image
    # today= strftime("%Y-%m-%d")
    # dir_path=os.path.join("predections",'figures',today)
    # if not os.path.isdir(dir_path):
    #     os.mkdir(dir_path)
    # graph_name= os.path.join("predections",'figures',today,f'{ticker}.jpg')
    # fig.savefig(graph_name, bbox_inches='tight', dpi=150)
    plt.show()

def plot_graph_multiple(df):
    df.plot( y=df.columns.values,use_index=True,kind="line", figsize=(10, 10))
    plt.show()

# creat_allocation("current")
# portfolio_name="current"
# # creat_allocation(portfolio_name)
# perf=calculate_performance(portfolio_name)
# perf['sum']= perf.sum(axis=1)
# perf['cum'] = (1 + perf['sum']).cumprod()
# print(perf)


#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project ：main.py
@File    ：Message.py
@IDE     ：PyCharm
@Author  ：DeamLake
@Date    ：2023/1/4 18:18
"""

from enum import Enum

class Message(object):
    def __init__(self, messageID, userID, content):
        self.messageID = messageID
        self.userID = userID
        self.content = content
        self.usersLike = set()

    messageIDAutoInc = 0
    messageList = []
    messageDic = {}

    @staticmethod
    def getNextID():
        Message.messageIDAutoInc += 1
        return str(Message.messageIDAutoInc)

    @staticmethod
    def getMessageByID(mid):
        if mid not in Message.messageDic:
            return None
        return Message.messageDic[mid]

    class MessageOptEnum(Enum):
        ADD_MSG = 1
        DEL_MSG = 2
        LIKE_MSG = 3
        UNLIKE_MSG = 4

    @staticmethod
    def updateMessage(message, optCode, optUserID):
        if optCode == Message.MessageOptEnum.ADD_MSG:
            Message.messageList.append(message)
            Message.messageDic[message.messageID] = message
        elif optCode == Message.MessageOptEnum.DEL_MSG:
            Message.messageList.remove(message)
            del Message.messageDic[message.messageID]
        elif optCode == Message.MessageOptEnum.LIKE_MSG:
            message.usersLike.add(optUserID)
        elif optCode == Message.MessageOptEnum.UNLIKE_MSG:
            if optUserID in message.usersLike:
                message.usersLike.remove(optUserID)

        Message.messageList.sort(key=lambda msg: len(msg.usersLike), reverse=True)

    @staticmethod
    def getMessagesByPageNum(pageNum):
        if (pageNum - 1) * 10 > len(Message.messageList):
            return None
        left = 10 * (pageNum - 1)
        return Message.messageList[left:left+10]


"""Module contains Task Details class"""

from sqlalchemy import Column, DateTime, String, ForeignKey
from models.base_model import Base, BaseModel

class Revision(BaseModel, Base):
    """Revision class"""
    __tablename__ = 'revisions'
    master_list_id = Column(String(30),
                            ForeignKey('masterlist.id'),
                            nullable=False)
    requested_by = Column(String(20), nullable=False)
    approved_by = Column(String(20), nullable=False)
    reason_for_revision = Column(String(255), nullable=False)
    revised_date = Column(DateTime, nullable=False)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)