Skip to content

Commit

Permalink
Merge pull request #83 from masanorihirano/takata/samples_market_share
Browse files Browse the repository at this point in the history
[samples] MarketShare
  • Loading branch information
masanorihirano authored Aug 30, 2023
2 parents 4c3ef0f + 3e47f30 commit 7cf1469
Show file tree
Hide file tree
Showing 15 changed files with 638 additions and 3 deletions.
3 changes: 2 additions & 1 deletion docs/source/reference/agents.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ Agents
agents.Agent
agents.HighFrequencyAgent
agents.FCNAgent
agents.ArbitrageAgent
agents.ArbitrageAgent
agents.MarketShareFCNAgent
11 changes: 11 additions & 0 deletions docs/source/user_guide/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ Json config
"timeWindowSize": JsonRandomFormat,
"orderMargin": JsonRandomFormat,
"marginType": "fixed" or "normal" (Optional; default fixed)
},
"MarketShareFCNAgents": {
"class": "MarketShareFCNAgent",
"extends": "FCNAgent"
},
"ArbitrageAgent": {
"class": "ArbitrageAgent",
Expand All @@ -102,4 +106,11 @@ Json config
"orderThresholdPrice": float,
"orderTimeLength": int (Optional, default 1),
},
"MarketMakerAgent": {
"class": "MarketMakerAgent",
"extends": "Agents",
"targetMarket": string required,
"netInterestSpread": float required,
"orderTimeLength": int optional; default 2,
}
}
2 changes: 1 addition & 1 deletion examples/fat_finger.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,4 @@
},
"nbformat": 4,
"nbformat_minor": 1
}
}
2 changes: 2 additions & 0 deletions pams/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
from .base import Agent
from .fcn_agent import FCNAgent
from .high_frequency_agent import HighFrequencyAgent
from .market_maker_agent import MarketMakerAgent
from .market_share_fcn_agent import MarketShareFCNAgent
from .test_agent import TestAgent
131 changes: 131 additions & 0 deletions pams/agents/market_maker_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Union

from pams.agents.high_frequency_agent import HighFrequencyAgent
from pams.market import Market
from pams.order import LIMIT_ORDER
from pams.order import Cancel
from pams.order import Order
from pams.utils.json_random import JsonRandom


class MarketMakerAgent(HighFrequencyAgent):
"""Market Maker Agent class
This class inherits from the :class:`pams.agents.Agent` class.
This agent submits orders to the target market at the following price:
:math:`\left\{\max_i(P^b_i) + \min_i(P^a_i) \pm P_f \times \theta\right\} / 2`
where :math:`P^b_i` and :math:`P^a_i` are the best bid and ask prices of the :math:`i`-th target market,
and :math:`P_f` is the fundamental price of the target market.
""" # NOQA

target_market: Market
net_interest_spread: float
order_time_length: int

def setup(
self,
settings: Dict[str, Any],
accessible_markets_ids: List[int],
*args: Any,
**kwargs: Any,
) -> None:
"""agent setup. Usually be called from simulator/runner automatically.
Args:
settings (Dict[str, Any]): agent configuration.
This must include the parameters "targetMarket" and "netInterestSpread".
This can include the parameters "orderTimeLength".
accessible_markets_ids (List[int]): list of market IDs.
Returns:
None
"""
super().setup(settings=settings, accessible_markets_ids=accessible_markets_ids)
if "targetMarket" not in settings:
raise ValueError("targetMarket is required for MarketMakerAgent.")
if not isinstance(settings["targetMarket"], str):
raise ValueError("targetMarket must be string")
self.target_market = self.simulator.name2market[settings["targetMarket"]]
if "netInterestSpread" not in settings:
raise ValueError("netInterestSpread is required for MarketMakerAgent.")
json_random: JsonRandom = JsonRandom(prng=self.prng)
self.net_interest_spread = json_random.random(
json_value=settings["netInterestSpread"]
)
self.order_time_length = (
int(json_random.random(json_value=settings["orderTimeLength"]))
if "orderTimeLength" in settings
else 2
)

def submit_orders(self, markets: List[Market]) -> List[Union[Order, Cancel]]:
"""submit orders.
.. seealso::
- :func:`pams.agents.Agent.submit_orders`
"""
orders: List[Union[Order, Cancel]] = []
base_price: Optional[float] = self.get_base_price(markets=markets)
if base_price is None:
base_price = self.target_market.get_market_price()
price_margin: float = (
self.target_market.get_fundamental_price() * self.net_interest_spread * 0.5
)
order_volume: int = 1
orders.append(
Order(
agent_id=self.agent_id,
market_id=self.target_market.market_id,
is_buy=True,
kind=LIMIT_ORDER,
volume=order_volume,
price=base_price - price_margin,
ttl=self.order_time_length,
)
)
orders.append(
Order(
agent_id=self.agent_id,
market_id=self.target_market.market_id,
is_buy=False,
kind=LIMIT_ORDER,
volume=order_volume,
price=base_price + price_margin,
ttl=self.order_time_length,
)
)
return orders

def get_base_price(self, markets: List[Market]) -> Optional[float]:
"""get base price of markets.
Args:
markets (List[:class:`pams.Market`]): markets.
Returns:
float, Optional: average of the max and min prices.
"""
max_buy: float = -float("inf")
for market in markets:
best_buy_price: Optional[float] = market.get_best_buy_price()
if (
self.is_market_accessible(market_id=market.market_id)
and best_buy_price is not None
):
max_buy = max(max_buy, best_buy_price)
min_sell: float = float("inf")
for market in markets:
best_sell_price: Optional[float] = market.get_best_sell_price()
if (
self.is_market_accessible(market_id=market.market_id)
and best_sell_price is not None
):
min_sell = min(min_sell, best_sell_price)
if max_buy == -float("inf") or min_sell == float("inf"):
return None
return (max_buy + min_sell) / 2.0
49 changes: 49 additions & 0 deletions pams/agents/market_share_fcn_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from typing import List
from typing import Union

from pams.agents.fcn_agent import FCNAgent
from pams.market import Market
from pams.order import Cancel
from pams.order import Order


class MarketShareFCNAgent(FCNAgent):
"""Market Share FCN Agent class
This agent submits orders based on market shares.
This class inherits from the :class:`pams.agents.FCNAgent` class.
"""

def submit_orders(self, markets: List[Market]) -> List[Union[Order, Cancel]]:
"""submit orders based on FCN-based calculation and market shares.
.. seealso::
- :func:`pams.agents.FCNAgent.submit_orders`
"""
filter_markets: List[Market] = [
market
for market in markets
if self.is_market_accessible(market_id=market.market_id)
]
if len(filter_markets) == 0:
raise AssertionError("filter_markets in MarketShareFCNAgent is empty.")
weights: List[float] = []
for market in filter_markets:
weights.append(float(self.get_sum_trade_volume(market=market)) + 1e-10)
return super().submit_orders_by_market(
market=self.get_prng().choices(filter_markets, weights=weights)[0]
)

def get_sum_trade_volume(self, market: Market) -> int:
"""get sum of trade volume.
Args:
market (:class:`pams.Market`): trading market.
Returns:
int: total trade volume.
"""
t: int = market.get_time()
time_window_size: int = min(t + 1, self.time_window_size)
volume: int = sum(market.get_executed_volumes(range(0, time_window_size)))
return volume
2 changes: 1 addition & 1 deletion pams/market.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def setup(self, settings: Dict[str, Any], *args, **kwargs) -> None: # type: ign
Args:
settings (Dict[str, Any]): market configuration. Usually, automatically set from json config of simulator.
This must include the parameters "tickSize" and either "marketPrice" or "fundamentalPrice".
This can include the parameter "outstandingShares".
This can include the parameter "outstandingShares" and "tradeVolume".
Returns:
None
Expand Down
Empty file.
72 changes: 72 additions & 0 deletions samples/market_share/config-mm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"simulation": {
"markets": ["Market-A", "Market-B"],
"agents": ["MarketShareFCNAgents", "MarketMakerAgent"],
"sessions": [
{ "sessionName": 0,
"iterationSteps": 100,
"withOrderPlacement": true,
"withOrderExecution": false,
"withPrint": true
},
{ "sessionName": 1,
"iterationSteps": 2000,
"withOrderPlacement": true,
"withOrderExecution": true,
"withPrint": true,
"maxHifreqOrders": 1
}
]
},

"Market-A": {
"class": "ExtendedMarket",
"tickSize": 0.00001,
"marketPrice": 300.0,
"outstandingShares": 25000,

"MEMO": "Required only here",
"tradeVolume": 90
},

"Market-B": {
"class": "ExtendedMarket",
"tickSize": 0.00001,
"marketPrice": 300.0,
"outstandingShares": 25000,

"MEMO": "Required only here",
"tradeVolume": 10
},

"MarketShareFCNAgents": {
"class": "MarketShareFCNAgent",
"numAgents": 100,

"MEMO": "Agent class",
"markets": ["Market-A", "Market-B"],
"assetVolume": 50,
"cashAmount": 10000,

"MEMO": "FCNAgent class",
"fundamentalWeight": {"expon": [1.0]},
"chartWeight": {"expon": [0.0]},
"noiseWeight": {"expon": [1.0]},
"noiseScale": 0.001,
"timeWindowSize": [100, 200],
"orderMargin": [0.0, 0.1]
},

"MarketMakerAgent": {
"class": "MarketMakerAgent",
"numAgents": 1,

"markets": ["Market-B"],
"assetVolume": 50,
"cashAmount": 10000,

"targetMarket": "Market-B",
"netInterestSpread": 0.02,
"orderTimeLength": 2
}
}
60 changes: 60 additions & 0 deletions samples/market_share/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"simulation": {
"markets": ["Market-A", "Market-B"],
"agents": ["MarketShareFCNAgents"],
"sessions": [
{ "sessionName": 0,
"iterationSteps": 100,
"withOrderPlacement": true,
"withOrderExecution": false,
"withPrint": true
},
{ "sessionName": 1,
"iterationSteps": 2000,
"withOrderPlacement": true,
"withOrderExecution": true,
"withPrint": true,
"maxHifreqOrders": 1
}
]
},

"Market-A": {
"class": "ExtendedMarket",
"tickSize": 5.0, "MEMO": "0.05% of 10,000 YEN",
"marketPrice": 300.0,
"outstandingShares": 25000,

"MEMO": "Required only here",
"tradeVolume": 90
},

"Market-B": {
"class": "ExtendedMarket",
"tickSize": 1.0, "MEMO": "0.01% of 10,000 YEN",
"marketPrice": 300.0,
"outstandingShares": 25000,

"MEMO": "Required only here",
"tradeVolume": 10
},

"MarketShareFCNAgents": {
"class": "MarketShareFCNAgent",
"numAgents": 100,

"MEMO": "Agent class",
"markets": ["Market-A", "Market-B"],
"assetVolume": 50,
"cashAmount": 10000,

"MEMO": "FCNAgent class",
"fundamentalWeight": {"expon": [1.0]},
"chartWeight": {"expon": [0.2]},
"noiseWeight": {"expon": [1.0]},
"noiseScale": 0.0001,
"timeWindowSize": [100, 200],
"orderMargin": [0.0, 0.1],
"marginType": "normal"
}
}
Loading

0 comments on commit 7cf1469

Please sign in to comment.