Skip to content

Commit

Permalink
Merge pull request #9 from midassystems/fix_margin_call
Browse files Browse the repository at this point in the history
Updated margin call to be based on maintenance margin.
  • Loading branch information
anthonyb8 authored Feb 12, 2025
2 parents b76bc94 + 034eb42 commit 1d43fab
Show file tree
Hide file tree
Showing 21 changed files with 365 additions and 89 deletions.
27 changes: 19 additions & 8 deletions midastrader/execution/adaptors/dummy/dummy_broker.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,20 @@ def __init__(
self.trade_id = 0
self.threads = []
self.positions: Dict[int, Position] = {}
self.unrealized_pnl: Dict[str, float] = {"account": 0}
self.margin_required: Dict[str, float] = {"account": 0}
self.liquidation_value: Dict[str, float] = {"account": 0}
self.unrealized_pnl: Dict[int, float] = {}
self.init_margin_required: Dict[int, float] = {}
self.maintenance_margin_required: Dict[int, float] = {}
self.liquidation_value: Dict[int, float] = {}
# self.unrealized_pnl: Dict[str, float] = {"account": 0}
# self.margin_required: Dict[str, float] = {"account": 0}
# self.liquidation_value: Dict[str, float] = {"account": 0}
self.last_trades: Dict[int, Trade] = {}
self.account = Account(
timestamp=0,
full_available_funds=capital,
net_liquidation=capital,
full_init_margin_req=0,
full_maint_margin_req=0,
unrealized_pnl=0,
)
self.return_account()
Expand Down Expand Up @@ -376,18 +381,24 @@ def _update_account(self) -> None:
impact = position.position_impact()

# Update postion specific account values
self.unrealized_pnl[str(instrument_id)] = impact.unrealized_pnl
self.margin_required[str(instrument_id)] = impact.margin_required
self.liquidation_value[str(instrument_id)] = (
impact.liquidation_value
self.unrealized_pnl[instrument_id] = impact.unrealized_pnl
self.liquidation_value[instrument_id] = impact.liquidation_value
self.init_margin_required[instrument_id] = (
impact.init_margin_required
)
self.maintenance_margin_required[instrument_id] = (
impact.maintenance_margin_required
)

# Update Account values
self.account.unrealized_pnl = sum(
value for value in self.unrealized_pnl.values()
)
self.account.full_init_margin_req = sum(
value for value in self.margin_required.values()
value for value in self.init_margin_required.values()
)
self.account.full_maint_margin_req = sum(
value for value in self.maintenance_margin_required.values()
)
self.account.net_liquidation = (
sum(value for value in self.liquidation_value.values())
Expand Down
1 change: 1 addition & 0 deletions midastrader/execution/adaptors/ib/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def __init__(self, symbols_map: SymbolMap, bus: MessageBus):
timestamp=0,
full_available_funds=0,
full_init_margin_req=0,
full_maint_margin_req=0,
net_liquidation=0,
unrealized_pnl=0,
)
Expand Down
4 changes: 2 additions & 2 deletions midastrader/structs/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Account:
full_init_margin_req: float
net_liquidation: float
unrealized_pnl: float
full_maint_margin_req: Optional[float] = 0
full_maint_margin_req: float
excess_liquidity: Optional[float] = 0
currency: Optional[str] = ""
buying_power: Optional[float] = 0.0
Expand Down Expand Up @@ -157,7 +157,7 @@ def check_margin_call(self) -> bool:
Returns:
bool: True if a margin call is triggered, False otherwise.
"""
return self.full_available_funds < self.full_init_margin_req
return self.full_available_funds < self.full_maint_margin_req

def to_dict(self, prefix: str = "") -> dict:
"""
Expand Down
136 changes: 97 additions & 39 deletions midastrader/structs/positions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ class Impact:
cash (float): The current cash value associated with the position.
"""

margin_required: float
init_margin_required: float
maintenance_margin_required: float
unrealized_pnl: float
liquidation_value: float
cash: float
Expand Down Expand Up @@ -57,7 +58,8 @@ class Position(ABC):
initial_cost: float = 0.0
market_value: float = 0.0
unrealized_pnl: float = 0.0
margin_required: float = 0.0
init_margin_required: float = 0.0
maintenance_margin_required: float = 0.0
liquidation_value: float = 0.0

def __post_init__(self):
Expand Down Expand Up @@ -94,7 +96,8 @@ def __post_init__(self):
self.calculate_initial_value()
self.calculate_initial_cost()
self.calculate_market_value()
self.calculate_margin_required()
self.calculate_init_margin_required()
self.calculate_maintenance_margin_required()
self.calculate_unrealized_pnl()
self.calculate_liquidation_value()

Expand Down Expand Up @@ -137,7 +140,14 @@ def calculate_unrealized_pnl(self) -> None:
pass

@abstractmethod
def calculate_margin_required(self) -> None:
def calculate_init_margin_required(self) -> None:
"""
Calculates the margin required to maintain the position.
"""
pass

@abstractmethod
def calculate_maintenance_margin_required(self) -> None:
"""
Calculates the margin required to maintain the position.
"""
Expand Down Expand Up @@ -190,7 +200,8 @@ def to_dict(self) -> dict:
"market_price": self.market_price,
"market_value": self.market_value,
"unrealized_pnl": self.unrealized_pnl,
"margin_required": self.margin_required,
"init_margin_required": self.init_margin_required,
"maintenance_margin_required": self.maintenance_margin_required,
"liquidation_value": self.liquidation_value,
}

Expand All @@ -216,7 +227,8 @@ def pretty_print(self, indent: str = "") -> str:
f"{indent}Market Value: {self.market_value}\n"
f"{indent}Unrealized P&L: {self.unrealized_pnl}\n"
f"{indent}Liquidation Value: {self.liquidation_value}\n"
f"{indent}Margin Required: {self.margin_required}\n"
f"{indent}Init Margin Required: {self.init_margin_required}\n"
f"{indent}Maintenance Margin Required: {self.maintenance_margin_required}\n"
)


Expand Down Expand Up @@ -245,6 +257,7 @@ class FuturePosition(Position):
"""

initial_margin: float = 0.0
maintenance_margin: float = 0.0

def __post_init__(self):
"""
Expand All @@ -258,6 +271,11 @@ def __post_init__(self):
if not isinstance(self.initial_margin, (int, float)):
raise TypeError("'initial_margin' must be of type int or float.")

if not isinstance(self.maintenance_margin, (int, float)):
raise TypeError(
"'maintenance_margin' must be of type int or float."
)

# Value constraints
if self.initial_margin < 0:
raise ValueError("'initial_margin' must be non-negative.")
Expand All @@ -276,10 +294,11 @@ def position_impact(self) -> Impact:
self.calculate_liquidation_value()

return Impact(
margin_required=self.margin_required,
unrealized_pnl=self.unrealized_pnl,
liquidation_value=self.liquidation_value,
cash=self.initial_cost * -1,
self.init_margin_required,
self.maintenance_margin_required,
self.unrealized_pnl,
self.liquidation_value,
self.initial_cost * -1,
)

def calculate_initial_value(self) -> None:
Expand Down Expand Up @@ -321,11 +340,19 @@ def calculate_unrealized_pnl(self) -> None:
* self.quantity_multiplier
)

def calculate_margin_required(self) -> None:
def calculate_init_margin_required(self) -> None:
"""
Calculates the margin required to maintain the position.
"""
self.margin_required = self.initial_margin * abs(self.quantity)
self.init_margin_required = self.initial_margin * abs(self.quantity)

def calculate_maintenance_margin_required(self) -> None:
"""
Calculates the margin required to maintain the position.
"""
self.maintenance_margin_required = self.maintenance_margin * abs(
self.quantity
)

def calculate_liquidation_value(self) -> None:
"""
Expand Down Expand Up @@ -388,7 +415,8 @@ def update(
self.calculate_initial_value()
self.calculate_market_value()
self.calculate_initial_cost()
self.calculate_margin_required()
self.calculate_init_margin_required()
self.calculate_maintenance_margin_required()
self.calculate_unrealized_pnl()
self.calculate_liquidation_value()

Expand All @@ -410,10 +438,11 @@ def update(
returned_cost = initial_cost - self.initial_cost

return Impact(
margin_required=self.margin_required,
unrealized_pnl=self.unrealized_pnl,
liquidation_value=self.liquidation_value,
cash=returned_cost + realized_pnl,
self.init_margin_required,
self.maintenance_margin_required,
self.unrealized_pnl,
self.liquidation_value,
returned_cost + realized_pnl,
)

def to_dict(self) -> dict:
Expand All @@ -425,6 +454,8 @@ def to_dict(self) -> dict:
"""
base_dict = super().to_dict()
base_dict.update({"initial_margin": self.initial_margin})
base_dict.update({"maintenance_margin": self.maintenance_margin})

return base_dict

def pretty_print(self, indent: str = "") -> str:
Expand All @@ -439,6 +470,8 @@ def pretty_print(self, indent: str = "") -> str:
"""
string = super().pretty_print(indent)
string += f"{indent}Initial Margin': {self.initial_margin}\n"
string += f"{indent}Maintenance Margin': {self.maintenance_margin}\n"

return string


Expand Down Expand Up @@ -482,10 +515,11 @@ def position_impact(self) -> Impact:
self.calculate_liquidation_value()

return Impact(
margin_required=self.margin_required,
unrealized_pnl=self.unrealized_pnl,
liquidation_value=self.liquidation_value,
cash=self.initial_cost * -1,
self.init_margin_required,
self.maintenance_margin_required,
self.unrealized_pnl,
self.liquidation_value,
self.initial_cost * -1,
)

def calculate_initial_value(self) -> None:
Expand Down Expand Up @@ -518,14 +552,23 @@ def calculate_unrealized_pnl(self) -> None:
self.market_price * self.quantity * self.quantity_multiplier
) - self.initial_cost

def calculate_margin_required(self) -> None:
def calculate_init_margin_required(self) -> None:
"""
Calculates the margin required for the equity position.
Note:
Margin is set to zero for equity positions.
"""
self.margin_required = 0
self.init_margin_required = 0

def calculate_maintenance_margin_required(self) -> None:
"""
Calculates the margin required for the equity position.
Note:
Margin is set to zero for equity positions.
"""
self.maintenance_margin_required = 0

def calculate_liquidation_value(self) -> None:
"""
Expand Down Expand Up @@ -589,7 +632,8 @@ def update(
self.calculate_initial_value()
self.calculate_market_value()
self.calculate_initial_cost()
self.calculate_margin_required()
self.calculate_init_margin_required()
self.calculate_maintenance_margin_required()
self.calculate_unrealized_pnl()
self.calculate_liquidation_value()

Expand All @@ -611,10 +655,11 @@ def update(
returned_cost = initial_cost - self.initial_cost

return Impact(
margin_required=self.margin_required,
unrealized_pnl=self.unrealized_pnl,
liquidation_value=self.liquidation_value,
cash=returned_cost + realized_pnl,
self.init_margin_required,
self.maintenance_margin_required,
self.unrealized_pnl,
self.liquidation_value,
returned_cost + realized_pnl,
)

def to_dict(self) -> dict:
Expand Down Expand Up @@ -707,10 +752,11 @@ def position_impact(self) -> Impact:
self.calculate_liquidation_value()

return Impact(
margin_required=self.margin_required,
unrealized_pnl=self.unrealized_pnl,
liquidation_value=self.liquidation_value,
cash=self.initial_cost * -1,
self.init_margin_required,
self.maintenance_margin_required,
self.unrealized_pnl,
self.liquidation_value,
self.initial_cost * -1,
)

def calculate_initial_value(self) -> None:
Expand Down Expand Up @@ -773,14 +819,23 @@ def calculate_unrealized_pnl(self) -> None:
else:
raise ValueError("Invalid action type. Must be 'BUY' or 'SELL'.")

def calculate_margin_required(self) -> None:
def calculate_init_margin_required(self) -> None:
"""
Calculates the margin required for the options position.
Note:
Margin is set to zero for options positions.
"""
self.init_margin_required = 0

def calculate_maintenance_margin_required(self) -> None:
"""
Calculates the margin required for the options position.
Note:
Margin is set to zero for options positions.
"""
self.margin_required = 0
self.maintenance_margin_required = 0

def calculate_liquidation_value(self) -> None:
"""
Expand Down Expand Up @@ -843,7 +898,8 @@ def update(
self.calculate_initial_value()
self.calculate_market_value()
self.calculate_initial_cost()
self.calculate_margin_required()
self.calculate_init_margin_required()
self.calculate_maintenance_margin_required()
self.calculate_unrealized_pnl()
self.calculate_liquidation_value()

Expand All @@ -865,10 +921,11 @@ def update(
returned_cost = initial_cost - self.initial_cost

return Impact(
margin_required=self.margin_required,
unrealized_pnl=self.unrealized_pnl,
liquidation_value=self.liquidation_value,
cash=returned_cost + realized_pnl,
self.init_margin_required,
self.maintenance_margin_required,
self.unrealized_pnl,
self.liquidation_value,
returned_cost + realized_pnl,
)

def to_dict(self) -> dict:
Expand Down Expand Up @@ -938,5 +995,6 @@ def position_factory(

if asset_type == SecurityType.FUTURE:
kwargs["initial_margin"] = symbol.initial_margin
kwargs["maintenance_margin"] = symbol.maintenance_margin

return asset_classes[asset_type](**kwargs)
Loading

0 comments on commit 1d43fab

Please sign in to comment.