├── tmp
└── README.md
├── routes
├── voice
│ ├── utils
│ │ ├── __init__.py
│ │ └── helpers.py
│ ├── services
│ │ └── __init__.py
│ └── __init__.py
├── scalper
│ ├── __init__.py
│ ├── routes.py
│ └── services
│ │ └── scalper_service.py
├── orders
│ ├── __init__.py
│ ├── utils
│ │ ├── formatters.py
│ │ └── helpers.py
│ ├── constants.py
│ ├── routes.py
│ ├── services
│ │ ├── market_feed.py
│ │ └── broker_service.py
│ └── validators
│ │ ├── order_validator.py
│ │ └── exchange_rules.py
├── dashboard
│ ├── __init__.py
│ ├── settings_service.py
│ ├── utils.py
│ ├── routes.py
│ └── market_data_service.py
├── home.py
├── __init__.py
├── logs.py
└── funds.py
├── static
└── js
│ ├── modules
│ ├── templates
│ │ ├── emptyWatchlist.html
│ │ ├── marketDepth.html
│ │ └── symbolListItem.html
│ ├── watchlistManager.js
│ ├── orderEntry
│ │ ├── utils
│ │ │ ├── formatters.js
│ │ │ └── validators.js
│ │ ├── services
│ │ │ ├── orderApi.js
│ │ │ ├── orderState.js
│ │ │ └── priceService.js
│ │ └── components
│ │ │ ├── OrderForm.js
│ │ │ ├── MarketDepth.js
│ │ │ ├── QuantityInput.js
│ │ │ └── PriceInput.js
│ ├── watchlistCore.js
│ └── marketDataDecoder.js
│ └── theme.js
├── prestart.sh
├── production_files
├── prestart.sh
├── openterminal.service
└── openterminal_nginx
├── templates
├── index.html
├── components
│ ├── watchlist
│ │ ├── _list.html
│ │ ├── _market_depth.html
│ │ ├── _item.html
│ │ └── _manage_modal.html
│ └── orders
│ │ ├── _market_depth.html
│ │ └── _order_form.html
├── register.html
├── login.html
├── logs.html
├── tradebook.html
├── positions.html
├── orderbook.html
├── about.html
└── funds.html
├── scheduler.py
├── triggerdb.py
├── .gitignore
├── requirements.txt
├── config.py
├── app.py
├── README.md
├── master_contract.py
├── models.py
├── extensions.py
└── Proposed Architecture.md
/tmp/README.md:
--------------------------------------------------------------------------------
1 | DUMMY File
--------------------------------------------------------------------------------
/routes/voice/utils/__init__.py:
--------------------------------------------------------------------------------
1 | # Initialize utils package
2 |
--------------------------------------------------------------------------------
/routes/voice/services/__init__.py:
--------------------------------------------------------------------------------
1 | # Initialize services package
2 |
--------------------------------------------------------------------------------
/routes/scalper/__init__.py:
--------------------------------------------------------------------------------
1 | from .routes import scalper_bp
2 |
3 | __all__ = ['scalper_bp']
4 |
--------------------------------------------------------------------------------
/routes/orders/__init__.py:
--------------------------------------------------------------------------------
1 | # routes/orders/__init__.py
2 | from flask import Blueprint
3 |
4 | orders_bp = Blueprint('orders', __name__)
5 |
6 | from . import routes
--------------------------------------------------------------------------------
/routes/dashboard/__init__.py:
--------------------------------------------------------------------------------
1 | from .routes import dashboard_bp
2 |
3 | # This allows other parts of the app to continue importing dashboard_bp as before
4 | __all__ = ['dashboard_bp']
--------------------------------------------------------------------------------
/static/js/modules/templates/emptyWatchlist.html:
--------------------------------------------------------------------------------
1 |
2 |
No watchlists created yet
3 |
Click the "Add" button to create your first watchlist
4 |
5 |
--------------------------------------------------------------------------------
/static/js/modules/watchlistManager.js:
--------------------------------------------------------------------------------
1 | // static/js/modules/watchlistManager.js
2 |
3 | const WatchlistManager = {
4 | init() {
5 | WatchlistCore.init();
6 | }
7 | };
8 |
9 | // Export for use in other modules
10 | window.WatchlistManager = WatchlistManager;
11 |
--------------------------------------------------------------------------------
/routes/home.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint, render_template
2 |
3 | home_bp = Blueprint('home', __name__)
4 |
5 | @home_bp.route('/')
6 | def home():
7 | return render_template('index.html')
8 |
9 | @home_bp.route('/about')
10 | def about():
11 | return render_template('about.html')
12 |
--------------------------------------------------------------------------------
/prestart.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Create and set permissions for the Gunicorn log directory
4 | mkdir -p /var/log/gunicorn
5 | chown -R www-data:www-data /var/log/gunicorn
6 | chmod 755 /var/log/gunicorn
7 |
8 | # Set ownership and permissions for the application directory
9 | chown -R www-data:www-data /var/python/openterminal
10 | chmod -R u+rwX,g+rX,o+rX /var/python/openterminal
11 |
12 |
--------------------------------------------------------------------------------
/production_files/prestart.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Create and set permissions for the Gunicorn log directory
4 | mkdir -p /var/log/gunicorn
5 | chown -R www-data:www-data /var/log/gunicorn
6 | chmod 755 /var/log/gunicorn
7 |
8 | # Set ownership and permissions for the application directory
9 | chown -R www-data:www-data /var/python/openterminal
10 | chmod -R u+rwX,g+rX,o+rX /var/python/openterminal
11 |
12 |
--------------------------------------------------------------------------------
/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'layout.html' %}
2 | {% block content %}
3 |
3 | {% if watchlist.items_list and watchlist.items_list|length > 0 %}
4 | {% for item in watchlist.items_list %}
5 | {% include 'watchlist/_item.html' %}
6 | {% endfor %}
7 | {% else %}
8 |
9 | No items in this watchlist
10 |
11 | {% endif %}
12 |
--------------------------------------------------------------------------------
/scheduler.py:
--------------------------------------------------------------------------------
1 | from app import create_app, schedule_task
2 |
3 | # Create the Flask application instance
4 | app = create_app()
5 |
6 | # Start the scheduler with the application context
7 | schedule_task(app)
8 |
9 | # Keep the script running
10 | try:
11 | # If you need to perform any periodic tasks within the app context, you can do so here
12 | app.logger.info("Scheduler started and running.")
13 | while True:
14 | pass # Keep the script alive
15 | except KeyboardInterrupt:
16 | pass
17 |
--------------------------------------------------------------------------------
/production_files/openterminal.service:
--------------------------------------------------------------------------------
1 | # /etc/systemd/system/openterminal.service
2 | [Unit]
3 | Description=Gunicorn instance to serve OpenTerminal Flask app
4 | After=network.target
5 |
6 | [Service]
7 | User=www-data
8 | Group=www-data
9 | WorkingDirectory=/var/python/openterminal
10 | Environment="PATH=/var/python/venv/bin"
11 |
12 | ExecStart=/var/python/venv/bin/python -m gunicorn --workers 4 --bind unix:/var/python/openterminal/openterminal.sock "app:create_app()"
13 | [Install]
14 | WantedBy=multi-user.target
15 |
--------------------------------------------------------------------------------
/triggerdb.py:
--------------------------------------------------------------------------------
1 | from flask import Flask
2 | from extensions import db
3 | from master_contract import download_and_store_json
4 | from config import Config
5 |
6 | # Create a Flask application with configuration
7 | def create_app():
8 | app = Flask(__name__)
9 | app.config.from_object(Config)
10 |
11 | # Initialize extensions
12 | db.init_app(app)
13 |
14 | return app
15 |
16 | if __name__ == '__main__':
17 | # Set up the app context
18 | app = create_app()
19 |
20 | # Trigger the master contract download manually
21 | with app.app_context():
22 | download_and_store_json(app)
23 |
24 | print("Master Contract download manually triggered.")
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Python
3 | *.pyc
4 | *.pyo
5 | *.pyd
6 | __pycache__/
7 | *.env
8 | *.venv
9 | *.vscode
10 |
11 | # Flask files
12 | instance/
13 | *.sqlite3
14 | *.db
15 | *.sqlite
16 |
17 | # Environment variables
18 | .env
19 |
20 | # Byte-compiled / optimized / DLL files
21 | __pycache__/
22 | *.py[cod]
23 | *$py.class
24 |
25 | # Unit test / coverage reports
26 | htmlcov/
27 | .tox/
28 | .coverage
29 | .cache
30 | nosetests.xml
31 | coverage.xml
32 | *.cover
33 |
34 | # Distribution / packaging
35 | .Python
36 | build/
37 | develop-eggs/
38 | dist/
39 | downloads/
40 | eggs/
41 | .eggs/
42 | lib/
43 | lib64/
44 | parts/
45 | sdist/
46 | var/
47 | wheels/
48 | *.egg-info/
49 | .installed.cfg
50 | *.egg
51 |
52 | # Virtual environments
53 | venv/
54 | ENV/
55 | env/
56 | env.bak/
57 | venv.bak/
58 |
59 | # IDE and Editor files
60 | .idea/
61 | .vscode/
62 |
--------------------------------------------------------------------------------
/routes/orders/utils/formatters.py:
--------------------------------------------------------------------------------
1 | # routes/orders/utils/formatters.py
2 | from typing import Dict, Any
3 | from datetime import datetime
4 |
5 | def format_order_response(response: Dict) -> Dict:
6 | """Format broker response for client"""
7 | try:
8 | return {
9 | 'status': response.get('status'),
10 | 'order_id': response.get('data', {}).get('orderid'),
11 | 'message': response.get('message'),
12 | 'timestamp': datetime.now().isoformat()
13 | }
14 | except Exception as e:
15 | return {
16 | 'status': 'error',
17 | 'message': str(e),
18 | 'timestamp': datetime.now().isoformat()
19 | }
20 |
21 | def format_price(price: Any) -> str:
22 | """Format price for display"""
23 | try:
24 | return f"{float(price):.2f}"
25 | except (ValueError, TypeError):
26 | return "0.00"
27 |
28 | def format_quantity(quantity: Any) -> str:
29 | """Format quantity for display"""
30 | try:
31 | return str(int(quantity))
32 | except (ValueError, TypeError):
33 | return "0"
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | annotated-types==0.7.0
2 | anyio==4.6.2.post1
3 | APScheduler==3.10.4
4 | asgiref==3.8.1
5 | blinker==1.8.2
6 | cachelib==0.13.0
7 | certifi==2024.8.30
8 | charset-normalizer==3.4.0
9 | click==8.1.7
10 | colorama==0.4.6
11 | distro==1.9.0
12 | dnspython==2.7.0
13 | eventlet==0.37.0
14 | Flask==3.0.3
15 | Flask-Cors==5.0.0
16 | Flask-Login==0.6.3
17 | Flask-Session==0.8.0
18 | Flask-SQLAlchemy==3.1.1
19 | flask-talisman==1.1.0
20 | Flask-WTF==1.2.2
21 | gevent==24.10.3
22 | greenlet==3.1.1
23 | groq==0.12.0
24 | gunicorn==23.0.0
25 | h11==0.14.0
26 | httpcore==1.0.7
27 | httpx==0.27.2
28 | idna==3.10
29 | itsdangerous==2.2.0
30 | Jinja2==3.1.4
31 | MarkupSafe==2.1.5
32 | msgspec==0.18.6
33 | packaging==24.2
34 | pydantic==2.10.1
35 | pydantic_core==2.27.1
36 | pytz==2024.2
37 | ratelimit==2.2.1
38 | redis==5.1.1
39 | requests==2.32.3
40 | setuptools==75.3.0
41 | six==1.16.0
42 | sniffio==1.3.1
43 | SQLAlchemy==2.0.35
44 | typing_extensions==4.12.2
45 | tzdata==2024.2
46 | tzlocal==5.2
47 | urllib3==2.2.3
48 | Werkzeug==3.0.4
49 | word2number==1.1
50 | WTForms==3.2.1
51 | zope.event==5.0
52 | zope.interface==7.1.1
53 |
--------------------------------------------------------------------------------
/routes/orders/constants.py:
--------------------------------------------------------------------------------
1 | # routes/orders/constants.py
2 |
3 | # Order Types
4 | REGULAR = "NORMAL"
5 | STOPLOSS = "STOPLOSS"
6 | COVER = "COVER"
7 | AMO = "AMO"
8 |
9 | # Order Varieties
10 | INTRADAY = "INTRADAY"
11 | DELIVERY = "DELIVERY"
12 | CARRYFORWARD = "CARRYFORWARD"
13 |
14 | # Price Types
15 | MARKET = "MARKET"
16 | LIMIT = "LIMIT"
17 | SL_MARKET = "STOPLOSS_MARKET"
18 | SL_LIMIT = "STOPLOSS_LIMIT"
19 |
20 | # Exchange Segments
21 | EQUITY_SEGMENTS = ["NSE", "BSE"]
22 | DERIVATIVE_SEGMENTS = ["NFO", "BFO", "MCX", "CDS"]
23 |
24 | # Order Duration
25 | DAY = "DAY"
26 | IOC = "IOC"
27 |
28 | # Order Side
29 | BUY = "BUY"
30 | SELL = "SELL"
31 |
32 | # API Endpoints
33 | PLACE_ORDER_ENDPOINT = "/rest/secure/angelbroking/order/v1/placeOrder"
34 | MODIFY_ORDER_ENDPOINT = "/rest/secure/angelbroking/order/v1/modifyOrder"
35 | CANCEL_ORDER_ENDPOINT = "/rest/secure/angelbroking/order/v1/cancelOrder"
36 |
37 | # Error Codes
38 | ERROR_INVALID_QUANTITY = "E001"
39 | ERROR_INVALID_PRICE = "E002"
40 | ERROR_INVALID_TRIGGER = "E003"
41 | ERROR_SESSION_EXPIRED = "E004"
42 | ERROR_MARKET_CLOSED = "E005"
43 | ERROR_INVALID_SYMBOL = "E006"
--------------------------------------------------------------------------------
/static/js/modules/templates/marketDepth.html:
--------------------------------------------------------------------------------
1 | ';
43 |
44 | // Assuming depthData has bids and asks arrays
45 | for (var i = 0; i < Math.max(depthData.bids.length, depthData.asks.length); i++) {
46 | var bid = depthData.bids[i] || { quantity: 0, price: 0, orders: 0 };
47 | var ask = depthData.asks[i] || { quantity: 0, price: 0, orders: 0 };
48 |
49 | html += '
' +
50 | '
' + formatters.formatQuantity(bid.quantity) + '
' +
51 | '
' + bid.orders + '
' +
52 | '
' +
53 | formatters.formatPrice(bid.price) + '
' +
54 | '
' +
55 | formatters.formatPrice(ask.price) + '
' +
56 | '
' + ask.orders + '
' +
57 | '
' + formatters.formatQuantity(ask.quantity) + '
' +
58 | '
';
59 | }
60 |
61 | html += '
';
62 | container.innerHTML = html;
63 | }
64 |
65 | function handlePriceClick(price) {
66 | // Dispatch event for price selection
67 | var event = new CustomEvent('depth-price-selected', {
68 | detail: { price: price }
69 | });
70 | container.dispatchEvent(event);
71 | }
72 |
73 | function clear() {
74 | if (container) {
75 | container.innerHTML = '';
76 | }
77 | currentSymbol = null;
78 | depthData = null;
79 | }
80 |
81 | return {
82 | init: init,
83 | update: update,
84 | clear: clear
85 | };
86 | })();
--------------------------------------------------------------------------------
/routes/orders/services/broker_service.py:
--------------------------------------------------------------------------------
1 | # routes/orders/services/broker_service.py
2 |
3 | import http.client
4 | import json
5 | from typing import Dict
6 | from flask import request
7 | from ..constants import (
8 | PLACE_ORDER_ENDPOINT,
9 | MODIFY_ORDER_ENDPOINT,
10 | CANCEL_ORDER_ENDPOINT
11 | )
12 |
13 | class BrokerService:
14 | def __init__(self):
15 | self.base_url = "apiconnect.angelone.in"
16 |
17 | async def place_order(self, order_data: Dict, access_token: str, api_key: str) -> Dict:
18 | """Place order with Angel One broker"""
19 | try:
20 | conn = http.client.HTTPSConnection(self.base_url)
21 |
22 | # Prepare payload
23 | payload = self._prepare_order_payload(order_data)
24 | #print('Prepared payload:', payload)
25 | # Prepare headers with provided tokens
26 | headers = self._prepare_headers(access_token, api_key)
27 | #print('Prepared headers:', headers)
28 | # Send request
29 | conn.request("POST", PLACE_ORDER_ENDPOINT, payload, headers)
30 |
31 | # Get response
32 | response = conn.getresponse()
33 | data = response.read()
34 | return json.loads(data.decode("utf-8"))
35 |
36 | except Exception as e:
37 | print(f"Error in broker service: {str(e)}")
38 | raise
39 |
40 | def _prepare_order_payload(self, order_data: Dict) -> str:
41 | """Prepare order payload for Angel One API"""
42 | payload = {
43 | "variety": order_data.get("variety", "NORMAL"),
44 | "tradingsymbol": order_data["symbol"],
45 | "symboltoken": order_data["token"],
46 | "transactiontype": order_data["side"],
47 | "exchange": order_data["exchange"],
48 | "ordertype": order_data["ordertype"],
49 | "producttype": order_data["producttype"],
50 | "duration": order_data.get("duration", "DAY"),
51 | "price": str(order_data.get("price", "0")),
52 | "squareoff": "0",
53 | "stoploss": "0",
54 | "quantity": str(order_data["quantity"]),
55 | "disclosedquantity": str(order_data.get("disclosedquantity", "0"))
56 | }
57 |
58 | # Add stop loss specific fields
59 | if order_data.get("variety") == "STOPLOSS":
60 | payload["triggerprice"] = str(order_data.get("triggerprice", "0"))
61 |
62 | return json.dumps(payload)
63 |
64 | def _prepare_headers(self, access_token: str, api_key: str) -> Dict:
65 | """Prepare request headers with authentication"""
66 |
67 | return {
68 | 'Authorization': f'Bearer {access_token}',
69 | 'Content-Type': 'application/json',
70 | 'Accept': 'application/json',
71 | 'X-UserType': 'USER',
72 | 'X-SourceID': 'WEB',
73 | 'X-ClientLocalIP': 'CLIENT_LOCAL_IP',
74 | 'X-ClientPublicIP': 'CLIENT_PUBLIC_IP',
75 | 'X-MACAddress': 'MAC_ADDRESS',
76 | 'X-PrivateKey': api_key
77 | }
78 |
79 | async def modify_order(self, order_data: Dict, access_token: str, api_key: str) -> Dict:
80 | """Modify an existing order"""
81 | pass
82 |
83 | async def cancel_order(self, order_id: str, access_token: str, api_key: str) -> Dict:
84 | """Cancel an existing order"""
85 | pass
--------------------------------------------------------------------------------
/static/js/modules/templates/symbolListItem.html:
--------------------------------------------------------------------------------
1 |